--╔════════════════════════════╗═════════════════════════════════════════════════════════════════════════════════════════════════#
--║   	  PLAYER THINKERS      ║ Player Specific Thinkers! usually related to grinding, but not always.
--╚════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════════════════════#
local RAIL = GS_RAILS if RAIL.blockload return end --
local RAILTYPE = GS_RAILS_TYPES
local DFSTATS = GS_RAILS_SKINS["Default"] --The default character S_SKIN.
local SS2 = SPR2F_SUPER if SS2==nil SS2 = FF_SPR2SUPER or 0 end

local print = function(text) --Temporary debug
	if (consoleplayer) and consoleplayer==players[0] and consoleplayer.name=="Golden Shine" CONS_Printf(consoleplayer, text) end
end

--Replace this locally with one that accounts for upsidedown rails/hangrails and vertical rails better.
local P_SpawnSkidDust = function(p, DIST) local s = p.mo if not s return end --
	local ANG = p.drawangle+ANGLE_90
	local rail = s.GSgrind and s.GSgrind.myrail or nil
	local UPSIDEDOWN,Z = 1,1
	
	DIST = (DIST or 32<<16)/FRACUNIT
	DIST = P_RandomRange(-DIST, DIST)*s.scale
	if rail and type(rail)=="userdata" and rail.valid
		if rail.GSVertRail
			Z = DIST --Randomized Z dist instead.
			DIST = max(-11*s.scale, min($, 11*s.scale)) 
			UPSIDEDOWN = 2
		elseif rail.GSupsidedown or rail.GSupsidedown
			Z = $+abs(s.height) --Higher!
			UPSIDEDOWN = -1
		end
	end
	local ghs = P_SpawnMobjFromMobj(s, FixedMul(DIST, cos(ANG)), FixedMul(DIST, sin(ANG)), Z, MT_GSRAILGFX)
	if ghs and ghs.valid
		if (leveltime%2)
			ghs.scale = $/2
		end
		ghs.renderflags = $|RF_SEMIBRIGHT
		ghs.state = S_SPINDUST1
		ghs.fuse = 25
		if UPSIDEDOWN==2
			P_Thrust(ghs, p.drawangle, (DIST/3))
		else
			ghs.momz = abs(DIST/7)*P_MobjFlip(s)*UPSIDEDOWN
		end
		ghs.sttfade = 17
		ghs.scalespeed = s.scale/25
		ghs.destscale = s.scale*3 return ghs --
	end
end

--Player thinker for rails! Requires "p.mo" to be valid.(s)
--Players have inputs, special animations, balancing, etc, so they need extra code compared to other mobjs.
RAIL.PlayerThink = function(p) 
	if (gamestate!=GS_LEVEL) or not p.valid return end local s = p.mo
	if not (s and s.valid and s.GSgrind) return end --mo.GSgrind is normally not made until rails actually become relevant.
	local GS = s.GSgrind if GS.cannotgrind==nil RAIL.SetupMobjRailVars(s) GS = s.GSgrind end --Re-setup failsafe!
	
	local SKIN = GS_RAILS_SKINS[s.skin]
	local STATS
	if SKIN 
		if SKIN["PlayerThink"] and (SKIN["PlayerThink"](p, s, GS, GS.myrail))==true return end --End early?
		STATS = SKIN["S_SKIN"]
		if GS.chasecam==false or (STATS and STATS.AUTOVERTICALAIM==false)
			GS.forceVaim = max(GS.forceVaim or 0, 2) --Always apply this!
		end
	elseif GS.chasecam==false
		GS.forceVaim = max(GS.forceVaim or 0, 2)
	end

	if GS.movedfactor and not ((GS.railswitching or 0) > 2)
		GS.movedfactor = false
		if s.movefactor==0
			s.movefactor = FRACUNIT --Setting movefactor to nil can apparently only be done in native playerthinkers?
		end
	end
	if GS.forceVaim --Forces free vertical aim if active.
		GS.forceVaim = max($-1,0)
		GS.OGAIM = p.aiming
	elseif GS.OGAIM!=nil
		p.aiming = GS.OGAIM	
		GS.OGAIM = nil
	end
	if (s.flags2&MF2_TWOD) or (twodlevel) or s.currentaxis
		GS.TWOD = true --2D status includes Axis2D.
	else
		GS.TWOD = false
		if p.homing
			if GS.homingANG==nil GS.homingANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy) end
		elseif GS.homingANG!=nil and not GS.grinding
			GS.homingANG = nil
		end
	end
	if p.solcam and p.solcam.GSdisabled
		p.solcam.emergencydisable,p.solcam.GSdisabled = false, false
	end
	if GS.walljump --Table indicator you've walljumped. GS.lastwall prevents you from attaching to the same rail for a bit!
		GS.walljump.timer = max($-1,0)
		GS.walljump.init = $+1
		GS.cannotgrind = min($, 2)
		if (GS.walljump.init > 76) and not GS.walljump.timer or not s.health
		or P_IsObjectOnGround(s) or p.powers[pw_carry] or P_PlayerInPain(p) or (s.eflags & MFE_SPRUNG)
			if GS.walljump.loopcam
				GS.docam = 0
			end	
			GS.walljump = nil
		elseif (p.pflags & (PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE)) 
			if (GS.walljump.init < 9)
				if GS.walljump.init==1 and (s.momz*P_MobjFlip(s) < 15*s.scale) and ((p.cmd.buttons & BT_JUMP) or GS.railjump) --Minor boost.
					P_SetObjectMomZ(s, ((abs(s.momz)<7*s.scale) and 7 or 5)<<16, true)
				end
				if GS.walljump.jumpang!=nil 
				and not (p.pflags & (PF_THOKKED|PF_SPINNING)) and (p.panim==PA_SPRING or p.panim==PA_JUMP or p.panim==PA_ROLL)
					if (p.cmd.buttons & (BT_SPIN|BT_CUSTOM1)) and not (GS.walljump.loopcam)
						p.drawangle = s.angle
						GS.walljump.jumpang = nil --Face same direction.
					else
						p.drawangle = GS.walljump.jumpang
						if GS.walljump.loopcam and (GS.TWOD!=true)
							if GS.chasecam==false p.cmd.sidemove = 0 end
							p.cmd.forwardmove = 0	
							P_InstaThrust(s, GS.walljump.jumpang, p.speed)
						end
					end
				end
			end
		end
	elseif p.GSANflags!=nil and not (GS.grinding and (GS.myrail and GS.myrail.GSVertRail)) 
		p.pflags = ($ & ~(PF_ANALOGMODE|PF_DIRECTIONCHAR))|p.GSANflags
		p.GSANflags = nil
	end
/*	if GS.nonANALOGcorrect!=nil and not (GS.walljump and GS.walljump.timer)
		if not (p.pflags & PF_ANALOGMODE) and not p.powers[pw_carry]
		and not P_IsObjectOnGround(s) and not P_PlayerInPain(p)
			p.cmd.angleturn = GS.nonANALOGcorrect/FRACUNIT
			s.angle = GS.nonANALOGcorrect
			if s.momx or s.momy
				p.drawangle = R_PointToAngle2(s.x, s.y, s.x+s.momy, s.y+s.momy)
			else
				p.drawangle = s.angle
			end
		end		
		GS.nonANALOGcorrect = nil
	end */
	if p.GSRO and p.GSRO.NewbieGrinder
		p.GSRO.NewbieGrinder = max($-1,0)
	end
	if GS.stretch --Stretch effects.
		RAIL.Stretch(s, GS, GS.stretch)
	end
	if GS.LTAB --Last rail Table. Keeps track of various things we've done.
		GS.LTAB.init = $+1
		if GS.LTAB.timer
			GS.LTAB.timer = max($-1,0)
			if GS.LTAB.timer
				if (GS.LTAB.GetUpAnimANG!=nil) and (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) and (GS.LTAB.init < 3)
				and not (p.pflags & (PF_THOKKED|PF_SLIDING|PF_SPINNING)) and not p.powers[pw_carry] 
				and not (p.speed > 34*s.scale) and not P_PlayerInPain(p)
					P_InstaThrust(s, GS.LTAB.GetUpAnimANG, 5*s.scale)
					p.drawangle = GS.LTAB.GetUpAnimANG
				end
				if P_PlayerInPain(p) or p.powers[pw_carry] or (s.eflags & MFE_SPRUNG) or GS.walljump
				or p.homing or not s.health
					GS.LTAB.timer = 0
				elseif not GS.grinding and not GS.railswitching
					RAIL.buttons(p, GS, STATS)
					if (p.pflags & (PF_SPINNING|PF_STARTDASH|PF_THOKKED|PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE)) 
					or p.panim==PA_ABILITY or p.panim==PA_ABILITY2
						if GS.jump==1 or GS.spin==1 or GS.custom1==1 or GS.custom2==1 or GS.custom3==1
							GS.LTAB.timer = 0
						end
					end
				end
			end
		elseif (GS.LTAB.init > 350) or not s.health --Can't imagine needing this for over 10 seconds.
			GS.LTAB = nil
		end
	end
	GS.alreadytrailed = nil
	
	if GS.leftrail --Literally just left the rail.
		RAIL.LEFTRAIL(p,s,GS)
	elseif (p.powers[pw_flashing]>102 and p.powers[pw_flashing]<106) and ((GS.grinding or 0) > 2) --Don't stay on rail if in pain!
		local ANG,HANG = p.drawangle, (RAIL.GrindState(s, "hang")) and true or false
		RAIL.ExitRail(s, true, true)
		P_DoPlayerPain(p)
		p.drawangle = ANG
		GS.cannotgrind = max($, 15)
		if HANG P_SetObjectMomZ(s, 85356) end --Come crashing down from hangrails rather than bouncing up.
		return --
	end

--	RAIL.RailDebug(p,s) --DEBUG function
	if GS.justtouchedrailthing --We touched an attached rail thing recently.
		GS.justtouchedrailthing = $-1
	end
	if GS.perfectcorner --Doing a perfect corner! Can't lose balance for a bit!
		GS.perfectcorner = $-1
	end
	if GS.cantcrouch --You can't crouch rn!
		GS.cantcrouch = (type(GS.cantcrouch)=="number") and $-1 or nil
	end
	if GS.hanghop --Special sidehop from a hangrail?
		GS.hanghop = $-1
	end
	if GS.animcall --Currently calling an animation for the PostThinkFrame. Can be a string or number.
		GS.animcall = (type(GS.animcall)=="number") and max($-1,0) or 0
	end
	if GS.failroll --When you lost your balance and fall off like an idiot.	
		RAIL.FAILROLL(p,s,GS)
	end
	
	local JUMPFLAGS,LOOPFLAGS,GRINDFLAGS,LOOP = 0,0,0, nil
	local BALANCING = false
	if GS.grinding  --Ahh, railgrinding, are we?
		p.powers[pw_noautobrake] = max($, 3) --Turn that crap off.

		if GS.leftrail return end --Lies.

		local GRINDING = GS.grinding --Remember this!
		local SIDE,FORWARD = GS.side or 0,GS.forward or 0
		local RO = p.GSRO
		LOOP = RAIL.LearnLoopType(GS.myrail)

		if not GS.JumpRamp s.rollangle = 0 end
		s.friction = FRACUNIT
		GS.cannotgrind = max($,2)
		RAIL.buttons(p, GS, STATS) --Record our button presses!
		GS.railminspeed = 0 --Set to 0 for players all the time.	
		
		--Okay! Rail is real, let's do this!
		if GS.myrail and GS.myrail.valid
			if SKIN
				if STATS
					if STATS.MINSPEED
					and not (LOOP and LOOP.walljump) and not ((GS.myrail.GSgrindflags or 0) & 524288)
						GS.railminspeed = abs(STATS.MINSPEED)
					end
					if type(STATS.SPEEDTRAIL)=="number" and (STATS.SPEEDTRAIL > 0) and not GS.speedtrail 
					and (abs(GS.railspeed or 0) > STATS.SPEEDTRAIL*s.scale)
						GS.speedtrail = true
					end
				end	
				if SKIN["PreGrindThinker"] and (SKIN["PreGrindThinker"](p, s, GS, GS.myrail))==true return end --
			end

			local rail,CAM = GS and GS.myrail
			local REVERSE,CONVEYOR,GAINING = RAIL.GrindRail(s, rail, GS) --First, the Mobj Thinker. If it fails, cancel here too.
			if not (REVERSE) or not (GS.myrail and GS.myrail.valid) return end

			rail = GS.myrail  --Can sometimes change last second.
			GS.LastRail = GS.myrail --Record this only for players.
			CONVEYOR = (rail.GSconveyor) and rail.GSconveyor<<16 or 0
			LOOP = RAIL.LearnLoopType(rail) --Update! The rail could've changed.
			JUMPFLAGS,LOOPFLAGS,GRINDFLAGS = rail.GSjumpflags or 0,rail.GSloopflags or 0,rail.GSgrindflags or 0
			if GS.crouchbutton and not (STATS and (STATS.CROUCH==false))
			or (abs(GS.railtilt or 0)>320) 
				if not (STATS and (STATS.CROUCH==false and not STATS.MUSTBALANCE==true))
					if not (GS.cantcrouch) 
					and not (GRINDFLAGS & (65536|131072)) and not (rail.GSVertRail)
					and not (LOOP and (LOOP.anim!=0)) and not (LOOPFLAGS & (131072|1048576))
						BALANCING = true --Full-range balancing is active?
					end
				end
			end
			
			s.movefactor = 0
			GS.movedfactor = true
			
			--Gradually set the camera straight if it's rotated.
			if GS.viewrollangle
				if not (GRINDFLAGS & 524288) and not (LOOPFLAGS & (524288|262144)) or not (abs(GS.viewrollangle) > ANG1/128)
					local PLUS = (LOOPFLAGS & 4194304) and -1 or 1
					if GS.badbalance==true or (abs(GS.railtilt or 0) >= 340)
						GS.viewrollangle = FixedDiv(FixedMul(($/180),45),46)*PLUS*180
					else
						GS.viewrollangle = FixedDiv(FixedMul(($/180),14),15)*PLUS*180
					end
					p.viewrollangle = GS.viewrollangle
				else
					p.viewrollangle,GS.viewrollangle = 0,0
				end
			else
				p.viewrollangle = 0
			end
			local Rspeed = abs(GS.railspeed) or 1
			if STATS
				if STATS.MUSTBALANCE==true and not (GS.cantcrouch) and not (GRINDFLAGS & (65536|131072))
				and not (GRINDFLAGS & (65536|131072)) and not (LOOP and (LOOP.anim!=0)) 
				and not (LOOPFLAGS & (131072|1048576)) and not (rail.GSVertRail)
					BALANCING = true
				end
				if (STATS.ACCELSPEED)
					if not GS.flipanim and (p.cmd.forwardmove > 32) and (GS.grinding > 3)
					and not GS.badbalance and not GS.justtouchedrailthing
					and not (rail.GSVertRail)
					and not (LOOP and LOOP.walljump)
					and not (GRINDFLAGS & 65536) --No TwistDrive/FocusGrind also turns off AccelSpeed
						local CAP = (STATS.ACCELCAP or DFSTATS.ACCELCAP)*s.scale
						local ACCEL = max(1, STATS.ACCELSPEED*max(1, s.scale/80)) if s.skin=="sms" and not p.hypermysticsonic CAP=-1 end
		
						if (Rspeed < CAP) and GS.railspeed!=nil
							if (Rspeed > CAP/2) --Diminish slightly as you near the cap.
								ACCEL = (Rspeed > CAP*2/3) and $*2/3 or $*3/4	
							end
							GS.railspeed = (GS.railspeed<0) and $-ACCEL or $+ACCEL
							Rspeed = abs(GS.railspeed) or 1
						end
					end
				end
			end
			local CAM = RAIL.RailCam(p,s, GS, STATS) --Activate rail camera.		
			if LOOP
				if LOOP.spawndust
					P_SpawnSkidDust(p, 48<<16)
				end
				if (LOOP.speedtrail) and (Rspeed > LOOP.speedtrail*s.scale)
					GS.speedtrail = GS.speedtrail or true
				end
				if LOOP.walljump
					GS.cantcrouch,GS.crouchgrind,GS.maxroll = 3,false,69
					BALANCING = false
				else
					GS.WJtag = nil
				end
				if (LOOP.anim!=0)
					if LOOP.anim=="context" --Context sensitive?
						if LOOP.walljump
							GS.forcesprite = SPR2_SKID
						elseif (GS.spinstate or (p.cmd.buttons & BT_SPIN))
						and not (GS.myrail and GS.myrail.GSnorolling)
							GS.forcesprite = SPR2_ROLL
							if not (GS.spinstate)
								GS.spinstate = true
								if s.skin!="adventuresonic"
									local SFX = skins[s.skin].soundsid[SKSSPIN]
									if not (SFX) SFX = sfx_spin end
									if p==displayplayer and not S_SoundPlaying(s, SFX)
										S_StartSoundAtVolume(nil, SFX, 77, p)
									else
										S_StopSoundByID(s, SFX)
									end
									S_StartSound(s, SFX)
								end
							end
						else
							if GS.spinstate and GS.myrail and GS.myrail.GSnorolling
								GS.spinstate = false
							end
							local SPRITE2 = SPR2_WALK
							if ((Rspeed+abs(CONVEYOR)) > 29*s.scale) or (GRINDFLAGS & 524288)
								SPRITE2 = SPR2_RUN
							end
							GS.forcesprite = SPRITE2
							if (abs(p.cmd.forwardmove) < 9) and (abs(p.cmd.sidemove) < 9)
								GS.railspeed = FixedDiv(FixedMul($, 30), 31)
							end
						end
					else
						GS.forcesprite = type(LOOP.anim)=="number" and LOOP.anim or SPR2_ROLL
					end
					GS.animcall = 1
				end
			else
				GS.WJtag = nil
			end
			
			--What state do we go into?
			if not (s.eflags & MFE_VERTICALFLIP) and rail.GShangrail
			or (s.eflags & MFE_VERTICALFLIP) and not rail.GShangrail
				local CUSTOMHANG = nil
				if s.state != S_PLAY_RIDE and s.health
					s.state = S_PLAY_RIDE
				end
				GS.viewrollangle = ((GS.rollangle or 0)/-3)
				if (GS.grinding < 7) --Can't jump off for a few frames.
					GS.railspin = 999
				end	
				GS.cantcrouch,GS.crouchgrind,GS.maxroll = 7,false,290 --Incase of sudden transitions.
				BALANCING = false
				GS.flipanim,GS.flip,GS.flipanimCD = nil			
				if SKIN
					if STATS and (STATS.FORCEHANGSPRITE)
						CUSTOMHANG = STATS.FORCEHANGSPRITE
					end
					if SKIN["CustomHangAnim"] 
						SKIN["CustomHangAnim"](p, s, GS, GS and GS.myrail)
					end
				end
				if rail.GSforcerailanim
					local ANIM,NUM,DONE = rail.GSforcerailanim, (rail.GSforcerailanim2) and 2 or 1, false
					for d = 1,NUM if DONE==true break end --
						if (d!=1) ANIM = rail.GSforcerailanim2 end
						for i = 1,2
							if type(ANIM)=="number"
								if P_IsValidSprite2(s, ANIM)
									GS.forcesprite,GS.animcall = ANIM, "force" 
									CUSTOMHANG = nil DONE = true
								end break --
							elseif (spr2names[ANIM]) and type(spr2names[ANIM])=="number"	
								if d==2
									rail.GSforcerailanim2 = spr2names[ANIM]
								else
									rail.GSforcerailanim = spr2names[ANIM]
								end
							else
								break --
							end
						end
					end
				end
				if (CUSTOMHANG) and P_IsValidSprite2(s, CUSTOMHANG)
					GS.forcesprite,GS.animcall = CUSTOMHANG, "force"
				end
			else
				if s.health	and (s.state!=S_PLAY_GSGRINDING)
					s.state = S_PLAY_GSGRINDING
				end
				GS.maxroll = (STATS and (STATS.AUTOBALANCE!=true)) and 300 or 200
				
				if GS.rollangle!=nil and not (CAM and CAM.valid) and (GS.TWOD!=true)
				and not ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304))
				and not ((GRINDFLAGS & 524288) and not (CONVEYOR))
					local PLUS = 1
					if s.mirrored
						PLUS = -1
					--	if p==displayplayer and CV_FindVar("renderer").value==2 and CV_FindVar("gr_models").string=="On"
					--		PLUS = 1 --Nope.
					--	end
					end					
					s.rollangle = GS.rollangle*PLUS --fallback.
				end	
				
				if GS.flipanim --Doing a Sonic Heroes style twist! I'll dub it the Twist Drive for the tutorial!
					GS.flipanim,GS.flipanimCD = max($-1,0),2
					if not GS.flipanim
						s.spritexoffset,s.spriteyoffset,GS.railWEAPPREV,GS.railWEAPNEXT = 0,0,0,0
						if GS.railjump and (GS.railjump < 11) and (p.cmd.buttons & BT_JUMP)
							GS.railjump = 0 --Buffer a jump after a flip?
						end
					else
						GS.animcall = "flipanim"
					end
				elseif GS.flipanimCD --Wait a tic or 2, bud.
					GS.flipanimCD = max($-1,0)
				else
					if GS.twistbutton==1 and (GS.railjump!=1) and (GRINDING > 6)
					and (GS.badbalance!=true) 
					and not (STATS and STATS.HEROESTWIST==0)
					and not (rail.GSVertRail)
					and not (LOOP and (LOOP.anim and (LOOP.anim!=0) or LOOP.walljump==true) )
					and not (LOOPFLAGS & (131072|1048576)) --Not upsidedown or got rail effects disabled?
					and not (GRINDFLAGS & (65536|131072|524288)) --Controls not disabled, and momentum allowed?
					and not (SKIN and SKIN["TwistDrive"] and SKIN["TwistDrive"](p, s, GS, rail)==true)
						GS.flipanim = 11 --Speen!
						GS.flip = not GS.flip
						GS.animcall = "flipanim"
						s.mirrored = (GS.flip) and true or false		
						s.spritexscale,s.spriteyscale,s.spriteyoffset = 56000,78500, 0
						GS.stretch = {timer = 12, Xspeed = 2200, Yspeed = 1300}
						local GSPEED = abs(GS.railspeed or 0)
						if (GSPEED < 75*s.scale) or (REVERSE==-1) --Tiny boost if you're moving slowly.
						or CONVEYOR and (abs(GS.railspeed+CONVEYOR) < 24*s.scale)
							local BOOST = (STATS and (STATS.HEROESTWIST)) and STATS.HEROESTWIST or 14 --Default!
							if (BOOST!=1) --1 means a visual-only Heroes Twist, added specifically for Modern Sonic.
								local ADD = BOOST*s.scale
								if REVERSE==-1 and (BOOST > 2)
									ADD = ($*5/4)+(5*s.scale) --Extra power to undo reversing!
								else
									if (GSPEED > 21*s.scale)
										ADD = $-min(ADD-(4*s.scale), GSPEED/15)
									end
									if GAINING==true
										ADD = max(1, ($*4)/9)
									end
								end
								ADD = $*(RAIL.GrindFlip(s, rail, true))
								GS.railspeed = $+ ADD //*(RAIL.GrindFlip(s, rail, true))
							end
						elseif (GSPEED > 88*s.scale) --On the other hand, best NOT spam this if you're moving fast already!
							GS.railspeed = FixedDiv(FixedMul($, 76), 77)
						end
					end
				end					
				if rail.GSforcerailanim
					if not (GS.flipanim) --Force a per-rail animation?
						local ANIM,NUM,DONE = rail.GSforcerailanim, (rail.GSforcerailanim2) and 2 or 1, false
						for d = 1,NUM if DONE==true break end --
							if (d!=1) ANIM = rail.GSforcerailanim2 end
							for i = 1,2
								if type(ANIM)=="number"
									if P_IsValidSprite2(s, ANIM)
										GS.forcesprite,GS.animcall = ANIM, "force" 
										DONE = true
									end break --
								elseif (spr2names[ANIM]) and type(spr2names[ANIM])=="number"	
									if (d!=1)
										rail.GSforcerailanim2 = spr2names[ANIM]
									else
										rail.GSforcerailanim = spr2names[ANIM]
									end
								else
									break --
								end
							end
						end	
					end
				elseif STATS and STATS.FORCESPRITE and not (LOOP and (LOOP.anim!=0)) and not (GS.flipanim)			
					if not (GS.railtossflag and ((p.cmd.buttons & BT_TOSSFLAG) or (GS.railtossflag < 99<<16)) )	
					or STATS.NOFUNALLOWED==true
						if P_IsValidSprite2(s, STATS.FORCESPRITE)
							GS.forcesprite,GS.animcall = STATS.FORCESPRITE, "force"
						end
					end
				end
				
				if SKIN and SKIN["CustomAnim"]
					local RESULT = SKIN["CustomAnim"](p, s, GS, GS and GS.myrail, false)
					if (GS.animcall!="flipanim")
						if RESULT==true
							GS.animcall = "force"
						elseif RESULT==false
							GS.animcall = "forcebutnotskip"
						end
					end
				end

				if (GS.animcall!="force")
					if P_IsValidSprite2(s, SPR2_GRND) --do a basic grinding pose!
					and not (LOOP and (LOOP.anim!=0))
					and not (GS.railtossflag and ((p.cmd.buttons & BT_TOSSFLAG) or (GS.railtossflag < 99<<16)))
						if (Rspeed > 22*s.scale)
							s.tics = 1	--fast anim
						elseif (s.tics < 0) or (s.tics > 2)
							s.tics = 2 --slow anim
						end
						if GS.animcall and type(GS.animcall)=="number"
							GS.animcall = 0
						end
					else --Do a Backup or Dunce pose!
						if not (GS.animcall)
							GS.animcall = 1
						end
						s.tics,s.anim_duration = -1,-1
					end
				end
			end
			p.powers[pw_justlaunched],p.powers[pw_justsprung],p.powers[pw_noautobrake] = 0,0,2
			p.speed = max(1, Rspeed)
			p.pflags = $|PF_JUMPDOWN & ~(PF_JUMPED|PF_SPINNING|PF_THOKKED|PF_STARTJUMP|PF_STARTDASH|PF_NOJUMPDAMAGE|PF_SLIDING|PF_SHIELDABILITY)
			
			local DOVIEWROLL = false
			--Crouching! Greater potential for speed retention and slopespeed, but balancing becomes important! 
			if (s.state==S_PLAY_GSGRINDING) --	if P_IsValidSprite2(s, SPR2_GRND)
			--	print("1! BALANCING:\x83"+BALANCING+"\x80 GS.crouchgrind:\x82"+GS.crouchgrind)
				if (BALANCING==true)  --Harder balancing but more speed payoff!
				and not GS.flipanim and not (STATS and STATS.CROUCH==false)
				and not GS.cantcrouch and not (GRINDFLAGS & (65536|131072))  --Crouch is temporarily disabled!
					if not GS.crouchgrind and not (STATS and STATS.CROUCH==false)
						S_StartSound(s, sfx_s3k52)
						GS.crouchgrind = true
						for d = 1,4
							P_SpawnSkidDust(p, 30<<16)
						end
					end
					if ((GS.railfalltimer or 0) > 3) and GS.badbalance==true //and (abs(GS.railtilt or 0) >= 300))
						DOVIEWROLL = true
					--	p.viewrollangle = GS.rollangle //(abs(GS.rollangle) > ANG1/64) and (GS.rollangle) or 0
					end 
					GS.maxroll = 800 --Balancing really comes into play now, so pay attention!
					s.spritexscale = min($+2500, 67001) s.spriteyscale = max($-2400, 63001)
					if (LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304) and not LOOP
						BALANCING = false
					end
				else
					if GS.crouchgrind and not GS.flipanim
						S_StartSound(s, sfx_s3k52)
						GS.stretch = {timer = 4, Xspeed = 2500, Yspeed = 2500, air = true}
					end
					GS.crouchgrind = false
				end
			end
			--Replicate vanilla's basic ugly spintrail, I guess?
			if GS.spinstate and LOOP or GS.forcesprite==SPR2_ROLL
				if not (LOOP and LOOP.endwithspin==false) and not (GS.LastRail and GS.LastRail.GSnorolling)
					p.pflags = $|PF_SPINNING
					if ((p.skin or 0) < 6) and (Rspeed > 16*s.scale) and ((p.spinitem or 0) <= MT_THOK)
						local ghs = P_SpawnMobjFromMobj(s, 0, 0, 1, MT_THOK)
						if ghs and ghs.valid
							ghs.color = s.color
						end
					end
				end
			end		
			--Sidehop section?
			if (JUMPFLAGS & 65536) and not (LOOP and LOOP.sidehop==false) and not (STATS and STATS.SIDEHOP==0)
			and not (p.powers[pw_nocontrol]) and (GS.TWOD!=true) and not (JUMPFLAGS & 131072) 
			and not ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304))
				RAIL.SpawnArrow(s, p.drawangle-(ANG20*4), SKINCOLOR_ORANGE, 6, -ANG20*4, true) --Sidehop tutorial arrow
			end
			if RO and RO.devmode and (s.eflags & MFE_SPRUNG) --Debugging for mappers.
			and not s.GScollide and not (GS.debugE1) and not GS.justtouchedrailthing and ((GS.grinding or 0) > 2)
				local XYDIST = 250<<16
				searchBlockmap("objects", function(ref, t)
					if t.valid and (t.flags & MF_SPRING) and not ((t.GSgrind) and (t.GSgrind.railobject))
						GS.debugE1 = true
						if p==displayplayer
							RAIL.error("\x85"+"Warning!\x80 Touched unattached spring/booster while grinding!"+"\n"+
							"\x82"+"For proper rail interactions, give your rails an Attach Objects to Rail flag!")
						end return true --
					end
				end, s, s.x-XYDIST, s.x+XYDIST, s.y-XYDIST, s.y+XYDIST)
			end
			GS.badbalance = false
			RAIL.PlayerTilt(p,s, rail, GS, STATS, GRINDFLAGS, LOOPFLAGS, BALANCING)	
			if DOVIEWROLL==true
				local PLUS,ROLL = GS.VRplus, GS.viewrollangle or 0
				local TILT = GS.railtilt or 0 if abs(TILT) > 800 TILT = ($<0) and -800 or 800 end
				TILT = $*(ANG1/10)
				if not (PLUS==-1 and (TILT > ROLL))
				and not (PLUS==1 and (TILT < ROLL))
					local ADD = ANG10/3
					if (TILT > ROLL)
						GS.viewrollangle = min(ROLL+ADD, TILT)
					else
						GS.viewrollangle = max(ROLL-ADD, TILT)
					end
				end
				GS.VRplus = (TILT < (GS.viewrollangle or ROLL)) and -1 or 1
			end
		end
		local VERTRAIL = ((GS.MyRailStats) and GS.MyRailStats.VERTRAIL or (rail) and rail.GSVertRail) or 0
		local HANG =  (RAIL.GrindState(s, "hang")) and true or false
		
		if SKIN and SKIN["GrindThinker"]
			if SKIN["GrindThinker"](p, s, GS, rail or GS.LastRail) end
		end
		if not GS or not (GS.myrail and GS.myrail.valid) --Aaand the rail is gone. Shit.
			RAIL.ExitRail(s, true, true) return --
		end
		
		--Grinding on a damaging Rail?
		if (GRINDFLAGS >= 2097152) and GS.myrail
		and not p.powers[pw_super] and not p.powers[pw_invulnerability] and not p.hypermysticsonic
		and not (p.xsonictable and p.xsonictable.darkphase==3) and not (p.solchar and p.solchar.istransformed)
			local STATE,SHIELD,DAMAGE  = s.state,(p.powers[pw_shield]&~SH_FIREFLOWER), 0	
			if (GRINDFLAGS & 4194304) and not (SHIELD and (SHIELD & SH_PROTECTELECTRIC)) --Electric Rail.
			and not ((s.skin=="xsonic" or s.skin=="xsonicserious") and p.xsonictable and p.xsonictable.PowerRing)
				P_DamageMobj(s, GS.myrail, GS.myrail, 1, DMG_ELECTRIC)
				if (s.state!=STATE) or p.powers[pw_flashing] or P_PlayerInPain(p)
					if p==displayplayer P_FlashPal(p, PAL_WHITE, 2) P_StartQuake(70<<16, 3) end
					S_StartSound(s, sfx_buzz2)
					DAMAGE = sfx_buzz1
					RAIL.SpawnFlameDust(s, SKINCOLOR_CYAN, s.scale*4)
				end
			end
			
			if (GRINDFLAGS & 2097152) and not (SHIELD and (SHIELD & SH_PROTECTFIRE)) --Burning Rail.
			and not (p.powers[pw_shield] & SH_FIREFLOWER) 
				P_DamageMobj(s, GS.myrail,GS.myrail, 1, DMG_FIRE)
				if (s.state!=STATE) or p.powers[pw_flashing] or P_PlayerInPain(p)
					DAMAGE = sfx_fire
					RAIL.SpawnFlameDust(s, nil, s.scale*3)
				end
			end
			
			if (DAMAGE!=0)
				S_StartSound(s, DAMAGE)  
				if s.health 
					s.state = S_PLAY_FALL
					p.pflags = $|PF_JUMPDOWN|PF_USEDOWN & ~(PF_STARTJUMP|PF_STARTDASH|PF_SPINNING|PF_JUMPED)
					if (p.powers[pw_flashing] > 100) and (p.powers[pw_flashing] < 200)
						p.powers[pw_flashing] = 88
					end
					GS.railtilt = ((GS.railtilt or 0)>0) and 800 or -800
					GS.railfalltimer = max(GS.railfalltimer or 0, 43)
					s.momz = $*2/3
				end
			end
		end
		
		--How should we get off the rail?
		if (GS.railfalltimer > 41) --Uhh, like a bumbling idiot? Got it.
			RAIL.FAILROLL(p,s,GS, true) return --
		else
			if P_PlayerInPain(p) or p.powers[pw_carry] and (p.powers[pw_carry]!=CR_GRINDRAIL) or GS.leftrail return end --Skip rest of the code.

			if rail and GS.grinding
				p.pflags = $|PF_JUMPDOWN & ~(PF_JUMPED|PF_SPINNING|PF_THOKKED|PF_STARTJUMP|PF_STARTDASH|PF_NOJUMPDAMAGE|PF_SLIDING|PF_SHIELDABILITY)
				if (GRINDFLAGS & 131072) --We're in a cinematic part, apparently.
					if (abs(GS.railtilt) > 190)
						GS.railtilt = ($<0) and -190 or 190
					end
					GS.railWEAPNEXT,GS.railWEAPPREV = 999<<16,999<<16
					GS.railjump,GS.railspin,railc3 = 999<<16,999<<16,999<<16 return --		
				end
			end
			if not (s.health or not p.playerstate==PST_LIVE)
				return --
			end

			--Custom3 is the drop-off-rail-button
			if GS.railc3 == 1
			and not (STATS and STATS.EMERGENCYDROP==false) and not (JUMPFLAGS & 262144)
				if SKIN and SKIN["CustomDislodge"] and SKIN["CustomDislodge"](p, s, GS, rail)==true
					return --
				end
				local FLOORZ = (s.eflags & MFE_VERTICALFLIP) and s.ceilingz or s.floorz
				if (abs(s.z-s.floorz) < 24*s.scale)
					s.z = $+5*s.scale*P_MobjFlip(s)
				end
				if HANG==true
					RAIL.ExitRail(s, "emergency", true)
					GS.cannotgrind = max($, 16)
					S_StartSound(s, sfx_s1b6)
					P_SetObjectMomZ(s, -4<<16, true)
					s.spriteyoffset = -6<<16
					s.spritexscale,s.spriteyscale = FRACUNIT-5000,FRACUNIT+9000
					GS.stretch = {timer = 5, Xspeed = 1700, Yspeed = 2000, air = true} return --No slope momentum on this.
				else
					RAIL.ExitRail(s, "emergency", true)
					s.spritexscale,s.spriteyscale = FRACUNIT+9000,FRACUNIT+9000
					GS.stretch = {timer = 3}
					s.momx,s.momy = $/3, $/3
					P_SetObjectMomZ(s, -1<<16)
					GS.cannotgrind = max($, 60)
					S_StartSound(s, sfx_gsral5)

					local ghs = P_SpawnGhostMobj(s) 
					ghs.scale = s.scale
					ghs.scalespeed = s.scale/12
					ghs.momx,ghs.momy = s.momx/2,s.momy/2
					ghs.destscale = 99<<16 ghs.blendmode = AST_ADD
				end
				if (s.momz*P_MobjFlip(s) > 5*s.scale)
					s.momz = 5*s.scale*P_MobjFlip(s)
				end

			elseif not ( GS.flipanim and HANG!=true and GS.animcall=="flipanim" and (GS.flipanim < 8))
			and not (GRINDFLAGS & 131072) --Controls ARE enabled, right?
			and not (LOOP and LOOP.jump==0) --This loop does allow jumps?
			
				local SIDEHOP = 0
				local HOPINPUT = (p.GSRO and p.GSRO.SideHold) and p.GSRO.SideHold or 0
				
				if GS.railWEAPNEXT==1 or GS.railWEAPPREV==1 --Do a sidejump like this?
					if HOPINPUT==2 or HOPINPUT==3 or HOPINPUT==4
						if (GS.TWOD!=true) and not (JUMPFLAGS & 131072) and not (LOOP and LOOP.sidehop==false)
							SIDEHOP = (GS.railWEAPNEXT==1) and 1 or -1
							GS.railjump = 1
							p.pflags = $|PF_JUMPDOWN
							p.cmd.buttons = $|BT_JUMP
						end
					end
				end
				if GS.railjump==1 or (SIDEHOP) --Jump off the rail? Let's call it a sidehop.
					local rail = GS.myrail or GS.lastrail
					local CANTSIDEHOP = false
					if (JUMPFLAGS & 131072) or (GS.TWOD==true)
					or LOOP and (LOOP.sidehop==false or LOOP.walljump) or (LOOPFLAGS & 131072)
						CANTSIDEHOP = true
					end
					if (abs(SIDE) > 44) and (abs(FORWARD) < 18) and (HOPINPUT!=2)
						if HOPINPUT==0 or HOPINPUT==3 or GS.railspin and (GS.railspin < 99<<16)
							SIDEHOP = (SIDE < 0) and -1 or 1
						end
					end
					local RAMPJUMP = false
					if rail and rail.GSjumprail
						RAMPJUMP = (rail.GSjumprail==1) and true or "frontiers"
					end
					
					--Jump off the rail in a normal way!
					if GS.railjump==1 and (not (SIDEHOP) or CANTSIDEHOP==true) and not GS.flipanim
					and not (JUMPFLAGS & 65536)
						local RETURNVALUE = nil
						if SKIN and SKIN["CustomJump"]
							RETURNVALUE = SKIN["CustomJump"](p, s, GS, rail or GS.LastRail, RAMPJUMP)
							if RETURNVALUE==true 
							or RETURNVALUE=="customramp" and RAMPJUMP==true
								return --
							end
						end
						if STATS and STATS.JUMP==false --Character can't jump!
							return --
						end
						
						local DOWNJUMP = false --Let players jump down if they hold SPIN. I hope this won't kill too many players...
						if HANG==true and not (JUMPFLAGS & 4194304)
							if (JUMPFLAGS & 8388608) 
							or (p.cmd.buttons & BT_SPIN) and not (LOOPFLAGS & 131072)  //and not (JUMPFLAGS & (2097152|1048576))
							and not (LOOP and LOOP.walljump) and not (rail and rail.GSVertRail) and (RAMPJUMP!="frontiers") 
								DOWNJUMP = true
							end
						end
						
						
						local ZDELTA = rail and rail.GSrailZDelta or 0
						local SLOPEFLING = RAIL.ExitRail(s, true, "jump")
						local OLDJUMPFACTOR = nil 
						local SCALE = max(FRACUNIT/4, s.scale)
						if (p.jumpfactor < 80350) and (p.jumpfactor > 9)
							if not (STATS and STATS.JUMPBOOST==false) --No jumpboost for this char.
								OLDJUMPFACTOR = p.jumpfactor  --There's an increased minimum jumpheight on rails.
								if ((SLOPEFLING or 0) >= 0) or RAMPJUMP==true
									p.jumpfactor = min(($*8/7)+2600, 80350)
								else
									p.jumpfactor = min(($*11/12)+400, 80350)
								end
							end
						end
						P_DoJump(p, true) --Apparently the SFX plays twice if PF_JUMPDOWN isn't enforced?
						if OLDJUMPFACTOR!=nil
							p.jumpfactor = OLDJUMPFACTOR
						end
						if RAIL.GrindState(s, "any") and s.health 
							s.state = S_PLAY_JUMP
						end
						GS.railswitchANG = p.drawangle --Useful for misc uses!
						p.pflags = $|PF_JUMPDOWN|PF_USEDOWN
						if GS.cannotgrind and ZDELTA and (abs(ZDELTA) > ANG10) and (s.momz*P_MobjFlip(s) > 12*SCALE)
							GS.cannotgrind = $+((abs(ZDELTA) > ANG20) and 2 or 1) --Extend it a bit when going down.
						end
					/*	if RAMPJUMP==true and RETURNVALUE!="norampjump"	
							if RETURNVALUE!="norampjump"
								
							end
						end */
						if (LOOPFLAGS & 131072)
						or RSTATS and RSTATS.VERTRAIL
						or GS.LastRail and GS.LastRail.GSVertRail
						or DOWNJUMP==true
							GS.railfalltimer,GS.railtilt = 0,0
							if s.health
								if LOOP and LOOP.walljump
									if (s.eflags & MFE_VERTICALFLIP) and (s.momz > -19*s.scale) and (DOWNJUMP!=true)
										s.momz = -19*s.scale
									end
								else
									if GS.railcam and GS.railcam.valid
										GS.docam = max(GS.docam or 0, 12)
									end
									if (GS.failroll) or (s.state==S_PLAY_PAIN)
										if GS.LastRail and GS.LastRail.valid and (p.speed < 44*SCALE) and (GS.TWOD!=true)
											P_Thrust(s, R_PointToAngle2(s.x, s.y, GS.LastRail.x, GS.LastRail.y), -4*SCALE)
										end	
									else
										if (LOOPFLAGS & 131072) or DOWNJUMP==true
											if GS.LastRail and GS.LastRail.GSVertRail
												if (p.speed < 24*SCALE) and GS.LastRail.valid
													P_InstaThrust(s, R_PointToAngle2(s.x, s.y, GS.LastRail.x, GS.LastRail.y), -24*SCALE)
												end
											else
												s.momz = -(abs($)+abs(SLOPEFLING))*P_MobjFlip(s)*((DOWNJUMP==true) and 2 or 3)
												if GS.LTAB  
													GS.LTAB.downjump = true
												end
											end
										end
										if LOOP and (LOOP.endwithspin!=false) and not (GS.LastRail and GS.LastRail.GSnorolling)
											s.state = S_PLAY_ROLL
											if LOOP.endwithspin==true
												if p.charability2 == CA2_SPINDASH
													p.pflags = $|PF_SPINNING|PF_USEDOWN
												end
											elseif LOOP.endwithspin=="force" or LOOP.endwithspin=="always"
												p.pflags = $|PF_SPINNING|PF_USEDOWN
											end
										else
											s.state = (LOOP and LOOP.anim==SPR2_ROLL or GS.spinstate) and S_PLAY_ROLL or S_PLAY_JUMP
											p.pflags = $|PF_JUMPED|PF_JUMPDOWN
										end
									end
								end
							end
						end	
						if ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304))
						and (p.cmd.forwardmove > 35) --Side camera!
						and (s.momx or s.momy) and not (p.pflags & PF_SPINNING)
							s.angle = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
							p.cmd.angleturn = s.angle/FRACUNIT
							if GS.railcam and GS.railcam.valid
								GS.railcam.angle = s.angle
							end
						end
						if JUMPFLAGS 	
							if (JUMPFLAGS & 2097152) or (JUMPFLAGS & 1048576) 
								if (DOWNJUMP!=true)
									S_StartSound(s, sfx_s1bb)
									S_StartSoundAtVolume(s, sfx_sprong, 88)
									local TYPE = RAILTYPE[RAIL.LearnRailType(GS.myrail or GS.LastRail)]
									if TYPE and not (TYPE.landSFX==sfx_none) --Clank! You hit a rail!
										local LANDSFX = RAILTYPE[0].landSFX if (TYPE.landSFX!=nil) LANDSFX = TYPE.landSFX end
										if not (TYPE.spawnleaves) and not (TYPE.nosparks)
											S_StopSoundByID(s, sfx_gsralg)
											S_StartSoundAtVolume(s, sfx_gsralg, 160) --The heroes experience.
										else
											S_StopSoundByID(s, LANDSFX)
											S_StartSoundAtVolume(s, LANDSFX, 124)
										end
									end	
									for i = 1,5 P_SpawnSkidDust(p, 62<<16) end
									if not (JUMPFLAGS & 2097152) --Super JumpHeight!
										s.momz = $+(10*SCALE*P_MobjFlip(s))
									else --Mega jumpheight! Stackable with Super for Titan jumpheight!
										s.momz = $+( ((JUMPFLAGS & 1048576) and 30 or 20)*SCALE*P_MobjFlip(s) )
										S_StartSoundAtVolume(s, sfx_mswing, 210)
									end
								end
							end		
						end
						
					elseif (SIDEHOP!=0) and (CANTSIDEHOP!=true) --Make rail switching leaps! 
						local rail = GS.myrail or GS.LastRail
						local CONVEYOR = 0 if rail and rail.GSconveyor CONVEYOR = rail.GSconveyor<<16 end
						local MOMANG = p.drawangle
					/*	if ((GS.railspeed or 0)+CONVEYOR < 0) and rail
							MOMANG  = RAIL.ANG(rail)
						else
							MOMANG  = RAIL.ANG(rail)+ANGLE_180
						end */
						
						
					--	print("Sidehop:\x83 "+SIDEHOP+"\x80 CONVEYOR: \x8c"+CONVEYOR/FRACUNIT+"\x80 GS.reversing:\x84"+GS.reversing)
						SIDEHOP = $*GS.invertinput --Quick and efficient, but you'll still want to use your brain and aim.
						
						local RESULT
						if SKIN and SKIN["CustomSideHop"] 
							RESULT = SKIN["CustomSideHop"](p, s, GS, rail or GS.LastRail, SIDEHOP, RAMPJUMP)
							if RESULT==true return end
						end
						local ZDELTA = GS.myrail and GS.myrail.GSrailZDelta or 0
						GS.preswitchspeed,GS.specialflip = GS.railspeed, nil --Save our previous speed!					

						local HOPSPEED = 35
						local SCALE = max(FRACUNIT/4, s.scale)
						if STATS
							if STATS.SIDEHOP==0 --Character can't sidehop!
								return --
							elseif (STATS.SIDEHOP)
								HOPSPEED = STATS.SIDEHOP+((HANG==true) and 2 or 0)
								if STATS.SIDEFLIP==7777
									if p.yusonictable p.yusonictable.noairdrag2 = min(p.yusonictable.noairdrag2 or 0, 10) end
									if GS.railtossflag or (GS.railWEAPPREV and GS.railWEAPNEXT)
										GS.specialflip = 13
										HOPSPEED = max(26, $-15)
									end
								end
							end
						end
						if HANG==true HOPSPEED = max($+5, 29) end	
						
						RAIL.ExitRail(s, true, "sidehop")--Get off the rail, using our current momentum.
						P_DoJump(p, true) --Jump.
						
						if RESULT!="nostretch"
							s.spriteyoffset = 9<<16 --Animation.
							s.spritexscale,s.spriteyscale = FRACUNIT+16000, FRACUNIT-8000
							GS.stretch = {timer = 4, Xspeed = 6000, Yspeed = 2800, air = true, OFFspeed = 2<<16}
							if RESULT=="hanghop" or RESULT=="hangflip"
								local TIME = 13 if STATS and STATS.SIDEHOP TIME = STATS.SIDEHOP+1 end
								GS.hanghop = TIME
							end
						end
						if not (CONVEYOR)
							if (s.momx or s.momy)
								MOMANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
							end
							if GS.reversing==-1 MOMANG = $+ANGLE_180 end
						end
						GS.switchCAMang = p.cmd.angleturn
						p.drawangle = MOMANG

						local THRUST = HOPSPEED*SCALE*-SIDEHOP
						if GS.preswitchspeed
							local PRES = abs(GS.preswitchspeed)
							if (PRES > 16*SCALE) --Can gain slightly stronger sidehops as your speed increases.
								local ADD = PRES/(16*SCALE)
								THRUST = FixedDiv(FixedMul($, 100+ADD), 100)
							end
						end
						if not (abs(s.momx)+abs(s.momy) > max(38, HOPSPEED+1)*s.scale)
							P_Thrust(s, MOMANG-(ANGLE_90*SIDEHOP), abs(THRUST))
						else
							P_Thrust(s, MOMANG-(ANG2*(53*SIDEHOP)), abs(THRUST))
						end
						
						s.momz,s.pmomz = 1,0 --Only slope momentum!
						GS.railswitching = (STATS and STATS.SIDEHOPTIME) and STATS.SIDEHOPTIME or 9
						if GS.railcam and GS.railcam.valid
							if STATS and STATS.SIDEHOPCAMTIME
								GS.docam = max(GS.docam or 0, STATS.SIDEHOPCAMTIME)
							elseif GS.specialflip
								GS.docam = max(GS.docam or 0, max(3, GS.railswitching))
							else
								GS.docam = max(GS.docam or 0, max(1, GS.railswitching-4))
							end
						end
						GS.justtouchedrailthing = max($, 2)
						GS.cannotgrind =  (HOPSPEED>50) and 1 or (HOPSPEED<20) and 5 or (HOPSPEED<35) and 3 or 2
						if (abs(ZDELTA or 0) > ANG10)
							GS.cannotgrind = $+((abs(ZDELTA) > ANG20) and 2 or 1)
						end
						
						GS.railswitchANG = p.drawangle
						p.powers[pw_nocontrol] = min($,2)
						S_StartSound(s, sfx_gsral9)
						if (HANG!=true)
							if GS.specialflip --Hyper-specialized flip!
								s.momz = $+(8*P_MobjFlip(s)*s.scale)
								GS.railswitching = max($, 13)
							elseif STATS and STATS.SIDEHOPARC 
								s.momz = $+(STATS.SIDEHOPARC*P_MobjFlip(s)*(s.scale/3))
							end
						end
						if RAIL.GrindState(s, "any") and s.health s.state = S_PLAY_JUMP end	
						p.pflags = $|PF_JUMPDOWN|PF_USEDOWN //|PF_SHIELDABILITY	
						if (s.scale < 50000)
							GS.cannotgrind = $+(5-(s.scale/10000)) --So they don't get stuck on the same rails for eternities.
						end
					end
				elseif p.GSRO and p.GSRO.HUDaid and (abs(SIDE) > 44) and (abs(FORWARD) < 18) and (HOPINPUT!=2)
					if not (LOOP and LOOP.sidehop==false) and not (STATS and STATS.SIDEHOP==0)
					and not (p.powers[pw_nocontrol]) and not GS.flipanim and (GS.TWOD!=true)
					and not ((HOPINPUT==1 or HOPINPUT==4) and not GS.railspin)
					and not (JUMPFLAGS & 131072) and not (rail and rail.GSVertRail)
					and not (s.GSarrow and s.GSarrow.valid and s.GSarrow.arrow)
						local ADDANG = ANG20*((p.cmd.sidemove*GS.invertinput > 0) and 4 or -4)
						RAIL.SpawnArrow(s, p.drawangle+ADDANG, SKINCOLOR_GOLDENROD, 6, ADDANG) --Sidehop tutorial arrow
					end
				end
			end
		end
			
		--Add manual slope momentum to our jumps!
		if not (GS.grinding) or not (GS.myrail and GS.myrail.valid) and not (p.powers[pw_justlaunched]==2)
			if (LOOPFLAGS & 131072) and LOOP
			or GS.LTAB and (GS.LTAB.downjump)
				if not (LOOP and LOOP.walljump)
					if (VERTRAIL)
						s.momz = $*7/8
						s.momx,s.momy = $*3/2,$*3/2
					else
						s.momz = (-abs($)*3/4)*P_MobjFlip(s)
						if GS.LTAB and GS.LTAB.downjump
							P_SpawnMobjFromMobj(s, 0,0,s.height, MT_EXPLODE)
							GS.cannotgrind = min($, 4)
							GS.LTAB.downjump = nil
							S_StartSound(s, sfx_s3ka6) S_StartSoundAtVolume(s, sfx_kc55, 155)
							if (p.speed < 88<<16)
								P_Thrust(s, s.angle, 5*s.scale)
							end
						else
							s.momx,s.momy = $*4/3,$*4/3
						end
					end
					GS.airdrag = nil
				end
			else
				RAIL.flingjump(s, GS, GRINDING, HANG)
			end
		else --Last possible things to do!
			if (p.charflags & SF_DASHMODE) and GS.grinding
				if ((GS.dashmode or 0) > 105)
					S_StopSoundByID(s, sfx_kc65)
				end
				if (GS.grinding > 5) and ((GS.grinding%2==0) or (GS.grinding%3==0))
					if ((GS.dashmode or 0) <= 105) --Even when grinding, you can burst into Dash Mode with alot of speed.
						local NEEDSPEED
						if STATS and (STATS.AUTODASHMODE!=nil)
							NEEDSPEED = STATS.AUTODASHMODE*s.scale
						else
							NEEDSPEED = DFSTATS.AUTODASHMODE*s.scale
						end
						if (NEEDSPEED) and (abs(GS.railspeed) > NEEDSPEED) --Burst into dash mode!
							S_StartSound(s, sfx_kc4d)
							p.dashmode = max(p.dashmode or 0, 108)
							GS.dashmode = max(GS.dashmode or 0, p.dashmode)
							local NORMALSPEED = skins[s.skin].normalspeed or 35<<16
							GS.normalspeed = max(p.normalspeed, max(GS.normalspeed or 0, NORMALSPEED*2))
						end
					elseif GS.normalspeed
						RAIL.DashModeOnRails(p,s,GS,rail) --Quick recreation of dash mode effect. Don't care enough for perfect accuracy	
						if (abs(GS.railspeed) < 22*s.scale) --Oops, you lost it!
							p.dashmode,GS.dashmode,GS.normalspeed = 0, nil,nil
							p.normalspeed = skins[s.skin].normalspeed
						end
					end
				end
			end
		end
		
	else --if not GS.grinding
	
		if GS.flipanim or GS.flip
			GS.flipanim,GS.flip,GS.flipanimCD = nil
			s.mirrored = false
			s.spriteyoffset = 0
		end
		--Are we railswitching? 	
		if GS.railswitching
			GS.ANGswap,GS.JumpRamp = nil
			GS.railswitching = max($-1, 0)
			if not (GS.cannotgrind > 4)
				GS.cannotgrind = min($, 4)
			end
			GS.justtouchedrailthing = max($, 2)
			RAIL.RailCam(p,s, GS, STATS) --Always access railcam here.
			
			if GS.railswitching --If it's still here, then...
				if GS.railswitchANG != nil and (GS.TWOD!=true)
					p.drawangle = GS.railswitchANG	
					if p.GSRO and ((p.GSRO.cam_autoturnspeed or 0) > 25)
						s.angle = GS.railswitchANG	
					end	
					p.cmd.angleturn = GS.railswitchANG>>16
				end
				
				local SPAWNLINES,SPAWNGHOSTS = true,true
				--Do a sideflip? Add an arc to the sidehop?
				if STATS 
					if STATS.NOSIDEHOPGFX
						if STATS.NOSIDEHOPGFX==1 or STATS.NOSIDEHOPGFX==3
							SPAWNLINES = false
						end
						 if STATS.NOSIDEHOPGFX==2 or STATS.NOSIDEHOPGFX==3
							SPAWNGHOSTS = false
						end
					end
					if (p.pflags & (PF_NOJUMPDAMAGE|PF_JUMPED))
						if (GS.railswitching < 6)
							local TMOMZ = s.momz*P_MobjFlip(s)
							if (TMOMZ > -8*s.scale)
								local MIN = 0
								if GS.specialflip and (STATS.SIDEFLIP==7777)
									MIN = 2*s.scale
								elseif STATS.SIDEHOPARC
									MIN = STATS.SIDEHOPARC*(s.scale/((STATS.SIDEHOPARC>17) and 14 or 11))
								end					
								if (MIN)
									if (TMOMZ < -5*s.scale) or GS.railswitching==5
										MIN = (TMOMZ < -10*s.scale) and $/2 or (TMOMZ < -8*s.scale) and $*2/3 or $*3/4
									end
									s.momz = $-(MIN*P_MobjFlip(s))
								end
							end
						end
						if (STATS.SIDEFLIP) and not ((STATS.SIDEFLIP==7777) and not GS.specialflip)
							GS.animcall = "sideflip"
							if not (GS.railswitching > 6)
								SPAWNLINES = false
							end
						end
					end
				end

				local MOMANG = p.drawangle if s.momx or s.momy MOMANG=R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy) end
				if SPAWNLINES==true
					RAIL.SpeedLines(s, GS, MOMANG, "leap")
				end

				local ghs
				for i = 1,2
					if (GS.railswitching > 4) or i==2 
						if SPAWNGHOSTS==true
							ghs = P_SpawnGhostMobj(s) --Sidehop after effects.
							P_SetObjectMomZ(ghs, 4<<16, true)
							
							ghs.scale = (s.scale*(9+min(0, GS.railswitching)))/10
							ghs.renderflags = $|RF_SEMIBRIGHT
							ghs.color,ghs.colorized = s.color or SKINCOLOR_YELLOW,true
							ghs.blendmode = AST_ADD
							ghs.fuse,ghs.tics = 7,8
							ghs.dispoffset = 77
							ghs.destscale = 1
							ghs.frame = ($ & ~FF_TRANSMASK) | min(7, max(1, 10-GS.railswitching))<<16
						end
						if i==2
							if SPAWNGHOSTS==true
								ghs.momx,ghs.momy = s.momx/-3,s.momy/-3
							end
							break --
						elseif SPAWNLINES==true
							RAIL.SpeedLines(s, GS, MOMANG+ANG20)			
						end
					end
					
					if s.health --Keep the right animation during all this!
						if not (p.pflags & PF_THOKKED)
							if p.panim==PA_ROLL or p.panim==PA_JUMP or p.panim==PA_SPRING
								if not (STATS and STATS.SIDEFLIP and (GS.railswitching > 1))
								or (STATS and STATS.SIDEFLIP==7777)
							--	(STATS.SIDEFLIP==SPR2_ROLL or STATS.SIDEFLIP==SPR2_JUMP or STATS.SIDEFLIP==SPR2_SPRING))
									s.state = S_PLAY_FALL
								end
							end
							if p.yusonictable p.yusonictable.sa2jumpdone = true end
						elseif p.panim==PA_FALL and (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE|PF_STARTJUMP)) 
							if (p.charability==CA_THOK or p.charability==CA_HOMINGTHOK) and not (p.charflags & SF_NOJUMPSPIN)
								s.state = S_PLAY_ROLL
							end
						end
					end
					if ghs or SPAWNLINES==false break end --
				end
			else
				if STATS and (STATS.SIDEFLIP) or GS.hanghop
					if not GS.JumpRamp s.rollangle = 0 end
				end
				if (p.charability==CA_THOK) or (p.charability==CA_HOMINGTHOK)
					if p.panim==PA_FALL and (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE))
					and not (p.pflags & PF_THOKKED) and not (p.charflags & SF_NOJUMPSPIN) and s.health
						s.state = S_PLAY_ROLL --Recurl when thokking or ready to thok!
					end
				end
			end
			
		else --if not GS.railswitching
		
			GS.ANGswap,GS.preswitchspeed,GS.specialflip = nil
			if GS.aimingreset and not (p.awayviewmobj and p.awayviewmobj.valid) --Gradually set p.aiming back to neutral! 
				if GS.OGAIM!=nil
					GS.aimingreset = 0
				else
					GS.aimingreset = max($-1,0)
					if not GS.aimingreset
						p.aiming = 0
					else
						local ABAIM = abs(p.aiming)
						if (ABAIM > ANG60)
							p.aiming = ($<0) and -ANG60 or ANG60
						elseif (ABAIM >= ANG10)
							p.aiming = (ABAIM < ANG20) and $*4/5 or (ABAIM < ANG20*2) and $*3/4 or $*2/3
						else
							p.aiming = $/2
						end
					end
				end
			end
			if GS.JumpRamp --Doing an SA2-style trick off a ramp!
				GS.JumpRamp.timer, GS.JumpRamp.init = $-1, $+1 
				local RAMP,INIT = GS.JumpRamp.timer,GS.JumpRamp.init
				if (INIT < 24) --Jump & cam properties.
					GS.airdrag = max(GS.airdrag or 0, 4)
					GS.aimingreset = max(GS.aimingreset or 0, 12)
					if (INIT < 15)
						if GS.OGAIM==nil
							if (s.eflags & MFE_VERTICALFLIP)
								p.aiming = min($, -ANG20)
							else
								p.aiming = max($, ANG20)
							end
						end
						if GS.JumpRamp.ropesnap
							GS.docam = 0
						else
							GS.docam = max(GS.docam or 0, 3)
						end
						p.pflags = $|PF_USEDOWN|PF_JUMPDOWN & ~PF_STARTJUMP	
						if not P_IsObjectOnGround(s)
							p.pflags = $|PF_THOKKED //PF_SHIELDABILITY|
						end
					end
				end
				if not RAMP or P_IsObjectOnGround(s) --Cancel conditions.
				or (p.pflags & (PF_SPINNING|PF_STARTDASH|PF_GLIDING|PF_SLIDING))
				or (p.panim==PA_PAIN) or not s.health or p.powers[pw_carry] or (s.eflags & MFE_SPRUNG) or not (s.health)
					if (s.momx or s.momy) and (p.panim!=PA_PAIN) and (GS.TWOD!=true) and not GS.JumpRamp.ropesnap
						p.drawangle = R_PointToAngle(s.x, s.y, s.x+s.momx, s.y+s.momy)	
					end
					GS.JumpRamp,s.rollangle,GS.FrontiersLeap = nil,0,nil
					if (p.pflags & PF_JUMPED|PF_NOJUMPDAMAGE) and s.health
						p.pflags = $ & ~(PF_SHIELDABILITY|PF_THOKKED) 
						if (s.state==S_PLAY_SPRING or s.state==S_PLAY_FALL) and not P_IsObjectOnGround(s)
							s.state = S_PLAY_JUMP
						end
					end
				else --Actual trick.
					local ADDROLL = (INIT<6) and ANG2 or (INIT<12) and ANG1*6 or (INIT>30) and ANG1*16 or ANG1*12
					if (RAMP < 39)
						s.state = (RAMP > 35) and S_PLAY_RIDE or (RAMP < 5) and S_PLAY_FALL or S_PLAY_SPRING
						if (RAMP < 16)
							ADDROLL = (RAMP < 10) and $/2 or (RAMP < 14) and $*2/3 or $*3/4					
						elseif (RAMP > 20)
							ADDROLL = (RAMP<22 or RAMP>36) and ANGLE_22h or (RAMP > 31) and ANG1*33 or ANGLE_45
						end
					else
						if (INIT > 26) ADDROLL = $*3/2 end
						s.state = (INIT > 10) and S_PLAY_RIDE or S_PLAY_SPRING	
					end
					if (INIT < 16)
						if (INIT < 10) --Little sparkles to indicate it's happening?
							local ghs = P_SpawnMobjFromMobj(s, 0, 0, s.height/2, MT_IVSP)
							ghs.mirrored = (leveltime%3==0) and true or false
							ghs.color = (leveltime%4==0) and s.color or (leveltime%2==0) and SKINCOLOR_SUPERSKY4 or SKINCOLOR_GOLDENROD
							ghs.colorized = true
							ghs.scalespeed = s.scale/64
							ghs.destscale,ghs.fuse = 1,48
							ghs.momx,ghs.momy,ghs.momz = s.momx/2,s.momy/2,s.momz/2
							ghs.flags = $|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOBLOCKMAP						
							if INIT==1 --Starting leap!
								GS.rollangle,GS.animcall,GS.failroll,GS.hangswing = 0,nil
								if (p==displayplayer) P_StartQuake(46<<16, 4) end
								S_StartSoundAtVolume(s, sfx_mswing, 99)
								S_StartSound(ghs, sfx_hoop2)
								p.pflags = $|PF_THOKKED & ~PF_STARTJUMP //PF_SHIELDABILITY|
								if (s.momx or s.momy) and (GS.TWOD!=true) --Face momentum angle, duh.
									GS.JumpRamp.STARTANG = R_PointToAngle2(s.x, s.y, s.x+s.momx, s.y+s.momy)
								end
								local TMOMZ = s.momz*P_MobjFlip(s)
								if (TMOMZ  < 64*s.scale)
									s.momz = $+(7*s.scale*P_MobjFlip(s))
									TMOMZ = s.momz*P_MobjFlip(s)
									if (TMOMZ  < 22*s.scale) --STILL tiny?? Okay, but put a limit on it!
										s.momz = FixedDiv(FixedMul($, 10), 9)+s.scale
									else
										S_StartSound(ghs, sfx_hoop3)
										if (TMOMZ > 48*s.scale) S_StartSoundAtVolume(s, sfx_nxbump, 210) end
									end
								end
								if (p.speed > 24*s.scale) and (TMOMZ > 32*s.scale) --Trade some speed for height!
									s.momx,s.momy = $*6/7,$*6/7
								end
							end
						end
						if (INIT%2==0)
							local ghs = P_SpawnGhostMobj(s)
							ghs.scale = (INIT%4==0) and s.scale or s.scale*5/4
							ghs.destscale = (INIT%4==0) and 1 or s.scale/2
							ghs.fuse,ghs.tics = 9,9
							ghs.blendmode = AST_ADD
							ghs.rollangle = s.rollangle
							ghs.color,ghs.colorized = (INIT%4==0) and SKINCOLOR_GOLDENROD or SKINCOLOR_SKY,true					
						end	
					end
					GS.JumpRamp.rollangle = $-ADDROLL
					GS.JumpRamp.ANG = $-(ADDROLL*2)	
					
					if (RAMP < 17) --Lock player angle and maybe also camera angle
						p.drawangle = GS.JumpRamp.STARTANG
					else
						if (GS.TWOD!=true) and not GS.JumpRamp.ropesnap
							s.angle = GS.JumpRamp.STARTANG
							p.cmd.angleturn = GS.JumpRamp.STARTANG>>16
						end	
						p.drawangle = GS.JumpRamp.STARTANG+(((RAMP >= 8) and (GS.TWOD!=true)) and GS.JumpRamp.ANG-ANG60 or 0)
					end
					s.rollangle = GS.JumpRamp.rollangle
				--	print("RAMP:\x83"+RAMP+"\x80 INIT:\x82"+INIT+"\x80 ROLL:\x82"+GS.JumpRamp.rollangle/ANG1)
				end
				
			elseif GS.FrontiersLeap --Sonic Frontiers style jump time?
				local DIRECT = max(abs(p.cmd.sidemove), abs(p.cmd.forwardmove))
				if P_IsObjectOnGround(s) or (p.pflags & (PF_SPINNING|PF_STARTDASH|PF_GLIDING|PF_SLIDING|PF_THOKKED))
				or (DIRECT < 5) and not p.powers[pw_nocontrol] or (s.eflags & MFE_SPRUNG)
				or p.powers[pw_carry] or p.panim==PA_PAIN or p.homing or (RAIL.busystates(s))
					GS.FrontiersLeap = nil
				else
					GS.FrontiersLeap,GS.airdrag = $-1,nil
					if (DIRECT > 30) and (p.cmd.buttons & BT_JUMP)
					and (p.speed > 21*s.scale) and (s.momz*P_MobjFlip(s) > s.scale) 
						if (p.speed <= 64*s.scale) and not (s.flags2&MF2_TWOD)
						and not (twodlevel) --Let's give some minor speeding-up potential
							P_Thrust(s,(p.cmd.angleturn<<16)+(R_PointToAngle2(0,0,p.cmd.forwardmove<<16,-(p.cmd.sidemove<<16))), DIRECT*(s.scale/48))
						end
						if (GS.FrontiersLeap > 3)
							local ghs = P_SpawnMobjFromMobj(s, 0, 0, -s.momz/2, MT_IVSP)
							ghs.mirrored = (leveltime%3==0) and true or false
							ghs.color = (leveltime%4==0) and s.color or (leveltime%2==0) and SKINCOLOR_SUPERSKY4 or SKINCOLOR_GOLDENROD
							ghs.colorized = true
							ghs.scalespeed,ghs.destscale = s.scale/64,1
							ghs.momx,ghs.momy = s.momx/3,s.momy/3
							P_Thrust(ghs, (leveltime*ANGLE_45), 48*s.scale)
							ghs.fuse = 33
							ghs.flags = $|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOBLOCKMAP
						end
					end
				end
			end
			if GS.airdrag --Since we're emulating Sonic Adventure, add a bit of airdrag after jumping off rails
				if P_IsObjectOnGround(s) or (p.pflags & (PF_SPINNING|PF_STARTDASH|PF_GLIDING|PF_SLIDING|PF_THOKKED))
				or (p.isxmomentum and not p.noxmomchar) or p.xsonictable and ((p.xsonictable.darkphase or 0)>1)
				or p.powers[pw_carry] or (p.panim>=PA_ABILITY) or p.panim==PA_PAIN or p.homing or (RAIL.busystates(s))
				or (STATS and STATS.AIRDRAG==0) or (s.eflags & MFE_SPRUNG)
					GS.airdrag = nil
				else
					local SPEED = (((GS.JumpRamp or 0) > 30) and 39 or 51)*s.scale
					if (p.speed > SPEED)
						if (p.speed > (SPEED*24/15))
							s.momx = FixedDiv(FixedMul($,98),99)
							s.momy = FixedDiv(FixedMul($,98),99)				
						else
							s.momx = FixedDiv(FixedMul($,204),205)
							s.momy = FixedDiv(FixedMul($,204),205)
						end
						GS.airdrag = max($-1,0)
					else
						GS.airdrag = max($-3,0)
					end
				end
			end
			if GS.docam --Keep using the rail camera during all this?
				RAIL.RailCam(p,s, GS)
			end		
		end	
		--Determine if we're in a state capable of landing on rails.	
		if p.powers[pw_carry]
		or ((p.homing or 0) > 92) and not (s.eflags & MFE_SPRUNG) and not (s.tracer and s.tracer.GSrailobject)
		or RAIL.busystates(s)
	--	or p.panim==PA_ABILITY and (p.charability==CA_FLY or (p.pflags & PF_GLIDING))
			GS.cannotgrind = max($,2) --NEVER grind like this!
			if p.powers[pw_carry]==CR_GRINDRAIL --3888
				p.powers[pw_carry] = 0
			end	
			if GS.railswitching
				if RAIL.busystates(s)
				or p.panim==PA_ABILITY and (p.charability==CA_FLY or (p.pflags & PF_GLIDING))
					GS.railswitching = 0 --Oops!
					if GS.animcall=="sideflip"
						GS.animcall = nil
					end
				end
			end
		else --Good to go! Count down!
			GS.cannotgrind = max($-1, 0)
		end	
		if GS.crouchgrind --Reset spriteyscale.
			GS.crouchgrind = false
			s.spriteyscale,s.spritexscale = FRACUNIT,FRACUNIT
		end
	end
	if GS.OGAIM!=nil
		if not (LOOPFLAGS & (4194304|524288|262144))
		and not (GRINDFLAGS & 131072) and not (LOOP and not LOOP.walljump)
			p.aiming = GS.OGAIM
			if p.awayviewmobj==GS.railcam and GS.railcam and GS.railcam.valid
				p.awayviewaiming = GS.OGAIM
			end
		end
		GS.OGAIM = nil
	end		
	if GS.nocam --Disable custom camera for a while.
		GS.nocam = $-1
	end
	if GS.docam --Enable custom camera for a while. Nocam takes priority.
		GS.docam = $-1
	end
end

--Actual behaviour for player tilting when GS.railtilt is not nil.
--An extension of the playerthinker, so I'll keep it here instead of in MOBJFUNC.
RAIL.PlayerTilt = function(p,s,rail, GS, STATS, GRINDFLAGS, LOOPFLAGS, BALANCING)
	if (GS.TWOD==true) --No balancing in 2D, naturally.
	or GS.leftrail --Not even on a rail!
	or (GRINDFLAGS & 131072) --Disabled controls	
	or ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304)) --Sidecam
		GS.ANGswap,GS.camadd = nil
		GS.railtilt,GS.perfectcorner,GS.badbalance = 0,0,false
		if (GS.TWOD==true) or GS.leftrail
			GS.railfalltimer = 0
		end
		return false --
	elseif (LOOPFLAGS & 131072) --Upsidedown loop
		GS.railtilt,GS.perfectcorner,GS.badbalance = 0,0,false
	end
	local HANG = (RAIL.GrindState(s, "hang")) and true or false
	local SIDE,FORWARD = GS.side or 0, GS.forward or 0	
	local LOOP = RAIL.LearnLoopType(GS.myrail)
	local AUTOBALANCE = (STATS and STATS.AUTOBALANCE==true) and true or false
	if p.powers[pw_super] or p.hypermysticsonic
	or p.solchar and p.solchar.istransformed or p.xsonictable and p.xsonictable.darkphase==3
	or GRINDFLAGS and (GRINDFLAGS & (262144|524288)) --Controls not disabled, and momentum allowed?
		AUTOBALANCE = "super"
		BALANCING = false
	end
	--When you swap rails, balancing comes into play!
	if GS.ANGswap
		local SWAP = GS.ANGswap
		if not SWAP.timer or not (SWAP.ANG) or (SWAP.ANG < 0)
			GS.ANGswap = nil
		else
			SWAP.init = $+1
			SWAP.timer = max($-1,0)
			
			if (SWAP.ANG > ANG1*89) SWAP.ANG=ANG1*89 end
			SWAP.ANG = ($ < ANGLE_45) and $*3/4 or $*2/3
			
			local ADD = SWAP.ANG/ ((ANG1/11)*SWAP.PLUS)
			if ADD
				if (SWAP.timer > 8)
					if GS.camadd==nil GS.camadd=0 end 
					local DIV = 64
					if GS.ANGswap.consec
						DIV = $-min(61, (p.cmd.buttons & (BT_SPIN|BT_CUSTOM1)) and GS.ANGswap.consec*7 or GS.ANGswap.consec*3)
					end
					local ADDCAM = (SWAP.ANG/DIV)*(SWAP.PLUS)
					if (SWAP.timer < 11) or (SWAP.init < 4)
						ADDCAM = ((ADD<0) and (GS.camadd>0) or (ADD<0) and (GS.camadd<0)) and $*2 or $
					else
						ADDCAM = ((ADD<0) and (GS.camadd>0) or (ADD<0) and (GS.camadd<0)) and $*4 or $*2
					end
					if (ADDCAM < 0)
						GS.camadd = max(-ADDCAM, min(ANGLE_22h, $-ADDCAM))
					else
						GS.camadd = min(-ADDCAM, max(-ANGLE_22h, $-ADDCAM))
					end
				end
				
				if HANG==true
					GS.hangswing = ANG1/3 --Prep this a bit.
					SWAP.ANG = $*2/3 --AGAIN!
					GS.railtilt = (GS.railtilt)+
					((abs(GS.railspeed or 0)>32*s.scale) and ADD*5 or (abs(GS.railspeed or 0)>32*s.scale) and ADD*4 or ADD*3)
					
					GS.badbalance,GS.railfalltimer = false,0	
					if (abs(GS.railtilt) >= 800)
						SWAP.timer = min(18, max($-6,0))
						GS.railtilt = ($ < 0) and -800 or 800
					else
						SWAP.timer = min(22, max($-2,0))
					end
				else
					local MAXROLL = (BALANCING==true or GS.crouchgrind) and 800 or 300
					if SIDE --You can save yourself by balancing.
						SIDE = $*GS.invertinput
						local MINS = -SIDE*2*(SWAP.PLUS or 1)
						if (MINS < 0)
							MAXROLL = 740
							if not (abs(SIDE) > 20)
								if SWAP.init==1
									GS.perfectcorner = max(GS.perfectcorner or 0, 2)
								end
							elseif not GS.flipanim --Gutsyness is rewarded!
								if SWAP.init==1 and GS.crouchbutton
									GS.perfectcorner = 7
								end
								if GS.crouchgrind or BALANCING==true
									if (abs(GS.railtilt) > 270) --Recover a bit!
										GS.railtilt = ($<0) and $+11 or $-11
									elseif GS.railtilt
										GS.railtilt = ($<0) and $+1 or $-1
									end
									if SWAP.init==1 and GS.railspeed and GS.LastGrindZ!=nil and (abs(GS.railtilt) < 464)
									and GS.crouchgrind
										local RLSPEED = abs(GS.railspeed)/s.scale
										if (RLSPEED < 280)
											if (RLSPEED > 120)
												local MAXB = RLSPEED
												GS.railspeed = FixedDiv(FixedMul($, MAXB), MAXB-1) --Perfect cornering!
											else
												if (RLSPEED < 42)
													GS.railspeed = ($<0) and $-2*s.scale or $+2*s.scale
												end
												GS.railspeed = FixedDiv(FixedMul($, 42), 41) 
											end
										end
										S_StopSoundByID(s, sfx_s1bc) S_StartSoundAtVolume(s, sfx_s1bc, 88)
										local ghs
										for i = 1,3
											ghs = P_SpawnMobjFromMobj(s,
											(i!=1) and FixedMul(-i*3*s.scale, cos(p.drawangle)) or 0,
											(i!=1) and FixedMul(-i*3*s.scale, sin(p.drawangle)) or 0, 0, MT_THOK)
											ghs.scale = (s.scale*6)/(i+4)
											ghs.fuse,ghs.tics = 7,9
											ghs.destscale = 99<<16
											ghs.scalespeed = s.scale/(5+i)
											ghs.spriteyoffset = -(11-(i*2))<<16
											ghs.blendmode = AST_ADD
											ghs.sprite = SPR_STAB
											ghs.frame = A|FF_PAPERSPRITE|FF_TRANS80
											ghs.angle = p.drawangle - (ANG10*(7-i))*(SWAP.PLUS or 1)
											ghs.flags = $|MF_NOCLIPHEIGHT|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING
											ghs.color,ghs.colorized = s.color,true
											if i != 1
												P_Thrust(ghs, p.drawangle-(ANG10*i)*(SWAP.PLUS or 1), -5*s.scale*(i*5))
											end
										end
									end
								end
							end
							ADD = max(0, $+MINS)
						end
					end
					if SWAP.init==1 and BALANCING==true and (MAXROLL != 740) and GS.railspeed and not GS.flipanim
						S_StopSoundByID(s, sfx_s1c0) S_StopSoundByID(s, sfx_s1bc) S_StartSoundAtVolume(s, sfx_s1c0, 130)
						GS.railspeed = FixedDiv(FixedMul($, 26), 27) --Bad cornering is punished!
						P_SpawnMobjFromMobj(s, 0, 0, s.height/3, MT_EXPLODE)
						for i = 1,3 P_SpawnSkidDust(p, 64<<16) end
					end
					if ADD
						GS.railtilt = $-ADD
						if (abs(GS.railtilt) > MAXROLL)
							GS.railtilt = ($ < 0) and -MAXROLL or MAXROLL
						end
					end				
				end
			end
			if GS.camadd and (SWAP.timer <= 8)
				if (SWAP.timer > 6) or (SWAP.timer < 3) or (LOOP)
					GS.camadd = ($<0) and min(0, $+ANG1) or ($>0) and max(0, $-ANG1) or 0	
				else
					GS.camadd = ($<0) and min(0, $+ANG2) or ($>0) and max(0, $-ANG2) or 0	
				end
			end
		--	print("\x80"+"SWAP.ANG added this tic:\x82"+ADD+"\x80 GS.railtilt is now:\x8a"+GS.railtilt+"\x80 Timer:\x8d"+SWAP.timer)
		end
	elseif GS.camadd
		local ANGX = (LOOP) and ANG2 or ANG1
		if GS.FreeAdd
			ANGX = $/(2+GS.FreeAdd)
			GS.FreeAdd = max($-1,0)
		end
		GS.camadd = ($<0) and min(0, $+ANGX) or ($>0) and max(0, $-ANGX) or 0	
	end
	if (HANG==true) or not GS.railtilt
		if GS.railfalltimer
			GS.railfalltimer = max((GS.railfalltimer>30) and $-6 or $-4, 0)	
		end
		if HANG==true --Swing about while hanging on stuff.
			if GS.rollangle and GS.ANGswap==nil
				if GS.hangswing==nil GS.hangswing = 0 end
				GS.hangswing = min(ANG20, (abs($)>ANG2*3) and $+ANG2 or (abs($)>ANG1*3) and $+ANG1 or (abs($)>ANG1) and $+ANG1/2 or $+(ANG1/3))
				
				--Realistic handswinging
				if (GS.rollangle < 0)
					GS.rollangle = $+GS.hangswing
					if (GS.rollangle >= 0)
						GS.hangswing = ((GS.grinding or 0) < 40) and $/-2 or $/-3
						if (abs(GS.hangswing) < ((abs(GS.rollangle) < ANG2) and (ANG1*3) or ANG1))
							GS.hangswing,GS.rollangle,GS.railtilt = 0,0,0
						end
					end
				else
					GS.rollangle = $-GS.hangswing
					if (GS.rollangle <= 0)
						GS.hangswing = ((GS.grinding or 0) < 40) and $/-2 or $/-3
						if (abs(GS.hangswing) < ((abs(GS.rollangle) < ANG2) and (ANG1*3) or ANG1))
							GS.hangswing,GS.rollangle,GS.railtilt = 0,0,0
						end
					end
				end
			else
				GS.hangswing = 0
				if GS.ANGswap==nil
					if GS.railtilt
						GS.railtilt,GS.hangswing = 0,0
					elseif ((GS.grinding or 0) < 12)
						GS.rollangle = ((SIDE>0) and -12 or (SIDE<0) and 12 or P_RandomRange(0,1)==1 and 12 or -12)*ANG1
						GS.hangswing = abs(GS.rollangle)
					end
				end
			end
			s.rollangle = GS.rollangle or 0
		--	print("HANGSWING:\x82"+(GS.hangswing/(ANG1))+"\x80 GS.rollangle:\x84"+GS.rollangle/ANG1+" lvltime:"+leveltime)
		end
	else
		--Tilting on rails! Mostly used for visual flair, but for characters with grinding sprites it can impact their speed!
		if (abs(GS.railtilt or 0) <= ((BALANCING==true or GS.crouchgrind) and 301 or 351)) 
	--		print("\x85 1"+abs(GS.railtilt))
	
			GS.railtilt = ($*7)/8 --We're not tilting much, so slowly auto-restabilize.
			GS.railfalltimer = (AUTOBALANCE!=false) and max($-2, 0) or 0
			if (abs(GS.railtilt) < 10) and (abs(SIDE) < 10)
				GS.railtilt = 0
			end

		else --Crap, crap, you'll fall at this rate!
			if AUTOBALANCE!=false
				GS.badbalance = false
				GS.railtilt = $*50/51
				GS.railfalltimer = 0
			else
			--	print("\x82 2"+abs(GS.railtilt))
				if (GS.railfalltimer > 30)
					if (leveltime%2)
						GS.railfalltimer = min($+1, 42)
					end
				elseif (GS.grinding > 8) and not (leveltime%3==0) 
					GS.railfalltimer = $+1
				end
				GS.badbalance = true
	
				if (GS.railfalltimer > 13)
					RAIL.FailDust(s, SKINCOLOR_EVENTIDE, 19, s.scale)
					if not S_SoundPlaying(s, sfx_s3kd1s) 
						S_StartSound(s, sfx_s3kd1s) 
					end
					if (leveltime%2==0)
						if (GS.railfalltimer > 25)
							s.flags2 = $|MF2_DONTDRAW
						end
						if (leveltime%4==0)
							for d = 24,12,-12
								if not (abs(GS.railspeed) < d*s.scale) break end --
								GS.railfalltimer = min($+1, 42)
							end
						end
					end
				end

				--Sweatdrops gfx.
				local RNG,RNG2 = P_RandomRange(-30,30),P_RandomRange(-31,31)
				local ghs = P_SpawnMobjFromMobj(s, 
				FixedMul(RNG*s.scale/2, cos(p.drawangle)),
				FixedMul(RNG*s.scale/2, sin(p.drawangle)), s.height+(abs(RNG)*15000), MT_GSRAILGFX)
				
				ghs.sprite = SPR_DRIP
				ghs.shadowscale = 58000
				ghs.frame = D|((leveltime%2) and FF_TRANS30 or FF_TRANS20)|FF_FULLBRIGHT
				ghs.rollangle = GS.rollangle+(RNG2*ANG1)
				ghs.scale = ($*15)/(30+abs(RNG))
				ghs.destscale = s.scale*9
				ghs.scalespeed = s.scale/12
				ghs.color,ghs.colorized = SKINCOLOR_SKY, true
				ghs.fuse,ghs.tics = 16,16

				ghs.sttfade = true
				ghs.GSgrinder = s
				P_SetObjectMomZ(ghs, (abs(RNG)*20000)+(s.z-GS.LastGrindZ), true)
				P_InstaThrust(ghs, p.drawangle+(RNG2*ANG2), (-GS.railspeed or 1)/2)
			end
		end	
	end	
	if GS.railtilt==nil return end 
	
	local MAXROLL = GS.maxroll or 0 
	if (abs(GS.railtilt) >= MAXROLL) MAXROLL = abs(GS.railtilt) end	
	
	--Now that we've determined automatic player tilts, get to the main tilting behaviour.
	local TILT = GS.railtilt
	if (abs(SIDE) > 4) and (abs(FORWARD) < 44) --Balancing!	
		local ADD = ((HANG==true) and SIDE/-2 or SIDE)
		if (GS.perfectcorner) and HANG!=true and ADD
			if (TILT < 0) and (TILT+ADD < TILT)
			or (TILT > 0) and (TILT+ADD > TILT)
				ADD = 0 --Perfect cornering means you can't make things worse for yourself for a moment.
			end
		end
		TILT = $+ADD
	end
	if AUTOBALANCE=="super"
		GS.railtilt = min(max(TILT, -299), 299) --Super Saiyan minmax values!
		GS.badbalance,GS.railfalltimer = false,0
		BALANCING = false
	else
		GS.railtilt = min(max(TILT, -MAXROLL), MAXROLL) --Minmax values!
	end	
	
	--Now set our final rollangle!	
	if not ((HANG==true) and GS.hangswing)
		if GS.badbalance and not (AUTOBALANCE)
			GS.rollangle = -GS.railtilt*(ANG1/15)
			if (leveltime%2==0)
				if (GS.rollangle < 0)
					GS.rollangle = $+ANG2*3
				else
					GS.rollangle = $-ANG2*3
				end
				P_SpawnSkidDust(p, 12<<16)
				if (p == displayplayer) and ((GS.railfalltimer or 0) > 33)
					P_StartQuake(min(800, abs(GS.railtilt))*420, 2)
				end
			end
		else
			GS.rollangle = -GS.railtilt*(ANG1/21) --Be more subtle if you're not in danger. 
		end
	end	
	if GS.rollangle 
		if ( ((LOOPFLAGS & (262144|524288)) and not (LOOPFLAGS & 4194304))
		or (LOOPFLAGS & 524288) or rail.GSVertRail or GS.ropesnap) and not GS.flipanim
			s.spritexoffset,s.spriteyoffset = 0,0
		else
			local PLUS = (s.mirrored) and -1 or 1
		--	if (LOOPFLAGS & 4194304) PLUS = $*-1 end
			if HANG==true PLUS = $*-1 end
			
			--Adjust xoffset since Lua sprite pivoting has special needs
			s.spritexoffset = max(-60, min(60, GS.rollangle/ANG1))*42000*PLUS
			if not ((GS.flipanim or 0) > 1)
				s.spriteyoffset = -(abs(s.spritexoffset or 1)/2)
			end
		end	
	end
	--Slow down according to your balance!
	if (abs(GS.railtilt) > 70) and (HANG!=true) and GS.railspeed and (AUTOBALANCE!="super")
		GS.balancepenalties = GS.balancepenalties and $+1 or 1
		if (GS.balancepenalties > ((BALANCING==true or GS.crouchgrind) and 4 or 1))
			local SMULT = 5950
			if (abs(GS.railspeed or 0) > 41*s.scale) and not (AUTOBALANCE) and (s.skin!="sms") 
				SMULT = (abs(GS.railspeed) > 70*s.scale) and 5250 or 5575
			end
			if GS.badbalance and not (AUTOBALANCE) --Huge penalty.
				GS.railspeed = FixedDiv( FixedMul($, SMULT), SMULT+abs(GS.railtilt/6) )
			elseif not GS.crouchgrind and not (BALANCING==true) --minor penalty if not GS.crouchgrind
				GS.railspeed = FixedDiv( FixedMul($, SMULT), max(SMULT, SMULT+abs(GS.railtilt/22)) )
			end
		end
	else
		GS.balancepenalties = 0
	end
end

--After losing balance on a rail.
RAIL.FAILROLL = function(p,s,GS, START)
	if (START)
		local tilt = GS.railtilt if not (tilt) tilt = (leveltime%2) and -499 or 499 end GS.railtilt = tilt
		local ang = p.drawangle
		local sideang = ang + ( ((tilt or 0)>0) and -ANGLE_90 or ANGLE_90)
		local flingspeed = max(abs(GS.railspeed or 1)/4, 8<<16)
		local TWOD = GS.TWOD
		RAIL.ExitRail(s, true, true) if not (s.health) return end --
		
		s.z = $+(16*s.scale*P_MobjFlip(s))
		if (TWOD!=true)
			P_TryMove(s, s.x+FixedMul(64*s.scale, cos(sideang )), s.y+FixedMul(64*s.scale, sin(sideang )), true)
		end
		
		s.momx,s.momy = $/3,$/3
		p.powers[pw_nocontrol] = max($+8, 8)
		GS.cannotgrind = max($, 24)
		if p==displayplayer
			S_StartSoundAtVolume(nil, sfx_kc55, 50, p) S_StartSoundAtVolume(nil, sfx_cdfm70, 50, p)
		end
		S_StartSound(s, sfx_cdfm70) S_StartSound(s, sfx_kc55)
		P_SetObjectMomZ(s, -3<<16, true)
		for d = 1,5
			P_SpawnSkidDust(p, 50<<16)
		end
		if tilt
			local MULT = (tilt>0) and 1 or -1
			if TWOD!=true
				P_Thrust(s, ang+ANGLE_90, -flingspeed*MULT)
			end
			GS.failroll = 19*MULT
			GS.failrollang = ang+(ANGLE_90*MULT)
		else
			GS.failroll = 17
		end
		GS.railjump = 999<<16
		p.pflags = $|PF_JUMPDOWN|PF_USEDOWN
		s.spritexscale,s.spriteyscale = FRACUNIT+7000,FRACUNIT-7000
		GS.stretch = {timer = 3, Xspeed = 2300, Yspeed = 2300, air = true}			
	end
	
	if not P_IsObjectOnGround(s)
	and ((START) or p.powers[pw_carry]==CR_GRINDRAIL or p.powers[pw_carry]==0)
	and not (p.pflags & (PF_STARTJUMP|PF_STARTDASH))
	and not ((p.pflags & (PF_NOJUMPDAMAGE|PF_STARTJUMP|PF_JUMPED)) and (p.pflags & PF_THOKKED))
	and not (P_PlayerInPain(p) and (p.powers[pw_flashing] > 99))
	and not (p.powers[pw_super]==1)
	and (s.health) 
	and (p.playerstate!=PST_DEAD)
	and (s.state!=S_PLAY_RIDE)
		if (GS.failroll < 0)
			GS.failroll = $+1
		elseif GS.failroll
			GS.failroll = $-1
		end
		GS.rollangle = GS.failroll*(ANG20)
		s.rollangle = GS.rollangle
		s.state = S_PLAY_PAIN
		if (abs(GS.failroll) > 8)
			if GS.failrollang != nil
				p.drawangle = GS.failrollang
			end
			GS.cannotgrind = max($, 3)
		end
	else
		GS.failroll,GS.failrollang = 0,nil
	end
	if not (GS.failroll)
		if p.panim == PA_PAIN and (s.health)
			s.state = S_PLAY_FALL
		end
		s.spritexoffset,s.spriteyoffset,s.rollangle = 0,0,0
		GS.failroll,GS.rollangle = 0,0
		GS.failrollang,GS.ANGswap = nil
	end
end

--Record a player's button inputs. Usually for getting off the rail in some way,
--but also for custom abilities, crouching, taunting, and heroes-style flipturns.
--STATS are optional.
RAIL.buttons = function(p, GS, STATS)	
	local BUTTONS = p.cmd.buttons or 0
	local rail = GS.myrail 
	if rail and ((rail.GSgrindflags & 131072) or p.powers[pw_nocontrol]) and GS.grinding and p.powers[pw_carry]==3888
		BUTTONS = 0
	end
	GS.railjump = (BUTTONS & BT_JUMP) and $+1 or 0
	GS.backupjump = (BUTTONS & BT_JUMP) and $+1 or 0
	GS.railspin = (BUTTONS & BT_USE) and $+1 or 0
	GS.backupspin = (BUTTONS & BT_USE) and $+1 or 0
	GS.railtossflag = (BUTTONS & BT_TOSSFLAG) and $+1 or 0
	GS.railc1 = (BUTTONS & BT_CUSTOM1) and $+1 or 0
	GS.railc2 = (BUTTONS & BT_CUSTOM2) and $+1 or 0
	GS.railc3 = (BUTTONS & BT_CUSTOM3) and $+1 or 0
	GS.railfire = (BUTTONS & BT_ATTACK) and $+1 or 0 --Some of these existing soley for custom coders/future support.
	GS.railnormalfire = (BUTTONS & BT_FIRENORMAL) and $+1 or 0
	GS.railWEAPPREV = (BUTTONS & BT_WEAPONPREV) and $+1 or 0
	GS.railWEAPNEXT = (BUTTONS & BT_WEAPONNEXT) and $+1 or 0	

	local TWISTBUTTON,CROUCHBUTTON = BT_SPIN, BT_SPIN	
	if STATS
		if (STATS.TWISTBUTTON)
			TWISTBUTTON = STATS.TWISTBUTTON
		end
		if (STATS.CROUCHBUTTON)
			CROUCHBUTTON = STATS.CROUCHBUTTON
			if STATS.TWISTBUTTON==0
				TWISTBUTTON = STATS.CROUCHBUTTON --Mimmick crouch button.
			end	
		end
	end 
	GS.twistbutton = (BUTTONS & TWISTBUTTON) and $+1 or 0
	GS.crouchbutton = (BUTTONS & CROUCHBUTTON) and $+1 or 0 --These two buttons can be different per character.	
	GS.invertinput = 1
	if (GS.grinding > 2) 
		if (GS.TWOD!=true) --Skip starting tics.
			local LOOPFLAGS = GS.LastRail and GS.LastRail.GSloopflags or 0
			if (LOOPFLAGS & 4194304)
				GS.invertinput = -1
				if not p.cmd.forwardmove and not p.cmd.sidemove
					p.cmd.forwardmove = -1
				end
			elseif not (LOOPFLAGS & (262144|524288))
				local CAMANG = p.cmd.angleturn<<16
				if (GS.railcam and GS.railcam.valid) and p.awayviewtics and p.awayviewmobj==GS.railcam
					CAMANG = GS.railcam.angle
				elseif GS.reversing==1 and GS.MyRailStats and ((GS.MyRailStats.ZSPEED or 0) < 0) and rail and rail.GSVertRail
					GS.invertinput = $*-1	
				end	
				local ANGDIFF = RAIL.AngleDifference(CAMANG, p.drawangle, 2, ANG1*89, 2)
				if (ANGDIFF > ANG1*69) --If the camera is backwards (69*2 = 138), invert the player's sideways inputs on some actions.
					GS.invertinput = $*-1
				end
				if not p.cmd.forwardmove and not p.cmd.sidemove
					p.cmd.forwardmove = 1
				end	
			end
		elseif not (p.cmd.sidemove)
			p.cmd.sidemove = (p.drawangle > ANGLE_90) and -1 or 1
		end
	end
end

--Adjusted expectations with Dash Mode?
RAIL.DashModeOnRails = function(p, s, GS, rail)		
	local SPEED,ghs = ((max(1, skins[s.skin].normalspeed or 0))*2/3) if not (SPEED) return end

	local Rspeed = abs(GS.railspeed) 
	SPEED = ((Rspeed >= $) and (GS.normalspeed >= $)) and "fast" or "slow"

	for i = -1,3,2
		if i==-1 --The Dashmode Overlay
			if (p.charflags & SF_MACHINE)
				ghs = P_SpawnMobjFromMobj(s, 0,0,0, MT_OVERLAY)
				ghs.tracer,ghs.target = s,s
				ghs.color = SKINCOLOR_KETCHUP	
				if s.skin ghs.skin=s.skin end ghs.sprite=s.sprite if s.sprite2 ghs.sprite2=s.sprite2 end
				ghs.frame = (s.frame & ~FF_TRANSMASK)|FF_FULLBRIGHT
				ghs.angle = p.drawangle		
				ghs.tics,ghs.fuse = 2,2
				A_CapeChase(ghs,1,0) 	
			end
		elseif i==1 --The Dashmode Afterimages.
			ghs = P_SpawnGhostMobj(s)
			P_Thrust(ghs, p.drawangle, -Rspeed)
			P_SetOrigin(ghs, ghs.x-FixedMul(Rspeed, cos(p.drawangle)), ghs.y-FixedMul(Rspeed, sin(p.drawangle)), ghs.z)
		else--if i==3 --The Dashmode FollowMobj Afterimages.	
			local FOLLOW = p.followmobj
			if FOLLOW and FOLLOW.valid and not (FOLLOW.skin) and (FOLLOW.sprite)
			and (FOLLOW.sprite!=SPR_PLAY) and not (FOLLOW.flags2 & MF2_DONTDRAW)
				local ghs = P_SpawnGhostMobj(FOLLOW)
				if s.skin=="metalsonic"
					FOLLOW.scale = max($, s.scale*2)
				end
				s = FOLLOW
				ghs.frame = (s.frame&~FF_TRANSMASK)|FF_TRANS40
				ghs.scalespeed = FRACUNIT/33
				ghs.destscale = 1
				if (leveltime%2==0)
					P_Thrust(ghs, p.drawangle, -Rspeed/2)
				end
			else
				break --
			end
		end
		if ghs and ghs.valid
			ghs.tics = 2+i
			ghs.scale = s.scale
			ghs.rollangle = s.rollangle
			ghs.spritexscale,ghs.spriteyscale = s.spritexscale, s.spriteyscale 
			ghs.spritexoffset,ghs.spriteyoffset = s.spritexoffset,s.spriteyoffset
			ghs.mirrored = s.mirrored
			if not ghs.fuse	
				ghs.color,ghs.colorized = s.color,s.colorized
				ghs.momx,ghs.momy,ghs.momz = $+s.momx, $+s.momy, $+s.momz
				ghs.fuse = min(16, ghs.tics or 1)
			end
		end
		if (SPEED=="slow") break end --
	end
end

--Animation Speed List. Anything not listed defaults to "1".
if RAIL.ANS==nil RAIL.ANS = {} end local ANS = RAIL.ANS
ANS[SPR2_WAIT] = 9
ANS[SPR2_JUMP] = 2
ANS[SPR2_GLID] = 2
ANS[SPR2_LAND] = 3
ANS[SPR2_RIDE] = 2
ANS[SPR2_CLNG] = 5
ANS[SPR2_CLMB] = 6
ANS[SPR2_BNCE] = 4
ANS[SPR2_FIRE] = 3
ANS[SPR2_TWIN] = 2
ANS[SPR2_MLEE] = 2
ANS[SPR2_WALK] = 2
ANS[SPR2_FALL] = 2
ANS[SPR2_SPNG] = 2
ANS[SPR2_FLY_] = 2
ANS[SPR2_SWIM] = 3
ANS[SPR2_TIRE] = 4
for i = SPR2_CNT1,SPR2_TAL4 --idk, this stuff varies heavily. Use 3, I guess.
	if i and type(i)=="number" ANS[i] = 3 end
end

RAIL.DOSKIN = function(SKIN,p,s,GS, animcall)
	if SKIN and SKIN["CustomAnim"] --Anything modders want to add on top of things.
		return SKIN["CustomAnim"](p, s, GS, GS and GS.myrail, true, animcall) 
	end	
end

--Plays a special animation for players, from the Post Think Frame. It's the "CustomAnim" hook, basically.
RAIL.CustomAnim = function(p, s, GS, FORCESPRITE)
	local OGCALL = GS.animcall
	local SKIN = GS_RAILS_SKINS[s.skin]
	local STATS = SKIN and SKIN["S_SKIN"]
	local SUPER = 0
	if (SPR2F_SUPER!=nil) and (s.sprite2&SPR2F_SUPER) --2.1.14+
		SUPER = SS2
	end
	if not (SUPER) and (p.powers[pw_super] or (s.eflags & MFE_FORCESUPER))
		SUPER = SS2
	end
--	print("Forcesprite:\x84"+GS.forcesprite+"\x80 GS.animcall:\x82"+GS.animcall+"\x80 GS.grinding:\x82"+GS.grinding+"\x80 GS.railtossflag:\x82"+GS.railtossflag)
	
	if GS.forcesprite --Force a certain sprite no matter what situation? GS.animcall must've been SOMETHING beforehand, tho.
		if FORCESPRITE==nil FORCESPRITE = GS.forcesprite end
		GS.animcall,GS.forcesprite = nil
	end	
	if type(GS.animcall)=="string"
		if GS.animcall == "sideflip" --For sidehopping.
			local FLIP = GS.railswitching or 0
			local SIDEFLIP = STATS and STATS.SIDEFLIP
			local FRAME = A
			if SIDEFLIP == 7777
				if s.skin=="adventuresonic" --Prevent issues for most characters!
					FRAME = (leveltime%3==0) and C or (leveltime%2==0) and B or A
					if p.yujumpauratable
						for d = 0,20,10
							if p.yujumpauratable[d] and p.yujumpauratable[d].valid
								P_RemoveMobj(p.yujumpauratable[d])
							end
						end
					end		
				end
				SIDEFLIP = (FLIP > 3) and SPR2_SPNG or SPR2_FALL
			elseif SIDEFLIP==SPR2_ROLL or SIDEFLIP==SPR2_SPIN or SIDEFLIP==SPR2_GLID --Some special sprites in this case!
				s.rollangle = 0
				GS.animcall,GS.curanim = 1,nil
				RAIL.CustomAnim (p, s, GS, SIDEFLIP) return --
			end
			if type(SIDEFLIP)=="number" and P_IsValidSprite2(s, SIDEFLIP)
				if s.sprite==SPR_PLAY
					s.sprite2 = SIDEFLIP|SUPER --Use this animation!
					s.frame = ($ & ~FF_FRAMEMASK)|FRAME
				end
				if (s.tics > 0) s.tics = max($, 5) end
				if (s.anim_duration > 0) s.anim_duration = max($, 5) end	
				
				if not (FLIP) or GS.railswitchANG==nil or SIDEFLIP==SPR2_MLEL
					s.rollangle = 0
					if SIDEFLIP==SPR2_MLEL
						local ANG = p.drawangle if GS.railswitchANG!=nil ANG = GS.railswitchANG end
						local _,P = RAIL.AngleDifference(ANG, R_PointToAngle2(s.x,s.y, s.x+s.momx,s.y+s.momy), 2, ANG60,2)
						p.drawangle = ANG+(FLIP*P*ANGLE_45) --+(ANGLE_135*P)
					end
				else
					local _,P = RAIL.AngleDifference(GS.railswitchANG, R_PointToAngle2(s.x,s.y, s.x+s.momx,s.y+s.momy), 2, ANG60,2)
					local ROT = max(0, min(355, min(11, FLIP)*35) )
					if ROT==0 or SIDEFLIP==SPR2_BOUNCE
						s.rollangle = 0
					else
						s.rollangle = (355-ROT)*ANG1*-P
					end
					p.drawangle = $+( max(0, min(ANG20*4, FLIP*ANG1*8)) *P) --Face somewhat sideways like in Unleashed.
				end
				if SIDEFLIP==SPR2_TWIN or SIDEFLIP==SPR2_BNCE or SIDEFLIP==SPR2_SWIM --More special sprites.
					GS.animcall,GS.curanim = 1,nil
					RAIL.CustomAnim (p, s, GS, SIDEFLIP) return --	
				end
			end	
			
			GS.animcall = 0
			return RAIL.DOSKIN(SKIN,p,s,GS, OGCALL) 
			
		elseif GS.animcall == "flipanim" --For the Sonic Heroes-style turn animation.
			local FLIP = GS.flipanim or 0
			if (FLIP >= 5)
				s.spriteyoffset = (FLIP>10) and $+11<<16 or (FLIP>9) and $+7<<16 or (FLIP>6) and $+4<<16 or (FLIP>5) and $+3<<15 or $+36500
			else --Falling down.
				s.spriteyoffset = max(0, (FLIP>=3) and $-3<<16 or (FLIP>=2) and $-5<<16 or $-8<<15)
			end
			if GS.myrail and GS.myrail.valid --Spin physically.
				local LOOPFLAGS = GS.myrail.GSloopflags or 0
				if (FLIP > 1)
					local STANCE = (FLIP > 5) and SPR2_RIDE and (FLIP > 3) and SPR2_SPNG or SPR2_FALL
					for i = 1,3
						if P_IsValidSprite2(s, STANCE) 
							if (s.sprite==SPR_PLAY)
								s.sprite2 = STANCE|SUPER 
								s.frame = $ & ~FF_FRAMEMASK
								s.tics,s.anim_duration = -1,-1
							end
							GS.animcall = 0 break --
						end STANCE = (i==1) and SPR2_SPNG or SPR2_FALL 
					end
					local ANG = RAIL.ANG(GS.myrail)+GS.raildirection
					if GS.flip
						p.drawangle = ANG+ min(10, FLIP)*(ANG2*16) - (ANG2*27)
					else
						p.drawangle = ANG+ min(11, FLIP)*(ANG1*39) - (ANG2*24)
					end	
					if FLIP==2 and not (LOOPFLAGS & 1048576)
						for i = 1,3 P_SpawnSkidDust(p, 32<<16) end
					end
				elseif not (LOOPFLAGS & 1048576)
					local TYPE = RAILTYPE[RAIL.LearnRailType(GS.myrail)]
					if TYPE
						if not (TYPE.landSFX==sfx_none) --Clank! You hit a rail!
							local LANDSFX = RAILTYPE[0].landSFX if (TYPE.landSFX!=nil) LANDSFX = TYPE.landSFX end
							if not (TYPE.spawnleaves) and not (TYPE.nosparks) and not (TYPE.electricrail)
								S_StopSoundByID(s, sfx_gsralg)
								S_StartSoundAtVolume(s, sfx_gsralg, 130) --The heroes experience.
							else
								S_StopSoundByID(s, LANDSFX)
								S_StartSoundAtVolume(s, LANDSFX, 104)
							end
						end
						for i = 1,5 P_SpawnSkidDust(p, 62<<16) end
					end
				end			
			end
		end	
		
		local RESULT = RAIL.DOSKIN(SKIN, p, s, GS, OGCALL) --What does the modder wanna do?
		if (RESULT!=nil)
			return RESULT
		elseif GS.animcall=="force" or GS.animcall=="flipanim" or GS.animcall=="stop"
			GS.animcall = 0 return  --
		else
			if P_IsValidSprite2(s, SPR2_GRND)
				if not (GS.railtossflag and ((p.cmd.buttons & BT_TOSSFLAG) or (GS.railtossflag < 99<<16)))
				or STATS and STATS.NOFUNALLOWED==true
					return -- This IS our animation!
				end
			end
		end	
	end
	
	--This is the default behaviour, whenever GS.animcall is a number, usually 1.
	local SPRITE2 = skins[s.skin].sprites
	if SPRITE2!=nil and (GS.grinding and not GS.flipanim or (FORCESPRITE))
		if GS.curanim --Stay in current animation first.
			if (GS.curanim.SKIN!=s.skin) or ((GS.curanim.MAX or 0)<2)
			or (GS.curanim.TIMER >= GS.curanim.MAX)
			or not (P_IsValidSprite2(s, GS.curanim.SPRITE2)) --and not (SUPER and P_IsValidSprite2(s, (GS.curanim.SPRITE2|SUPER)))
				GS.curanim = nil --NO WAY!
			else
				s.sprite = GS.curanim.SPRITE
				s.sprite2 = GS.curanim.SPRITE2
				s.frame = GS.curanim.FRAME & ~(FF_SPR2MIDSTART|FF_ANIMATE|FF_RANDOMANIM|FF_GLOBALANIM|FF_SPR2ENDSTATE)
				s.anim_duration,s.tics = -1,-1
				GS.curanim.TIMER = $+1 return --
			end
		end
		if (s.sprite!=SPR_PLAY) return end --Must be in SPR_PLAY!
		
		local STANCE = SPR2_STND
		local RO = p.GSRO
		if FORCESPRITE!=nil
			STANCE = FORCESPRITE
		elseif RO
			if not GS.railtossflag --What stance to use?
				STANCE = RO.backupgrindstate or SPR2_ROLL
		--		print("BACKUPSTATE:"+spr2names[STANCE]) //+"\x86 SPRITE2.numframes:"+SPRITE2.numframes)
			else
				STANCE = RO.duncegrindstate or SPR2_EDGE
		--		print("DUNCESTATE:"+spr2names[STANCE]) //+"\x86 SPRITE2.numframes:"+SPRITE2.numframes)
				if GS.railtossflag==1
					local ghs = P_SpawnMobjFromMobj(s,s.momx,s.momy,s.momz+s.height/9,MT_GSRAILGFX)
					ghs.color,ghs.colorized = s.color,true
					ghs.sttfade = true
					ghs.state = S_JETFUME1
					ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS50
					ghs.fuse,ghs.tics = 6,6
					ghs.scale = $*4/3
					ghs.scalespeed = (s.scale/12)*5
					ghs.destscale = 99<<16
					P_Thrust(ghs, p.drawangle, abs(GS.railspeed or 1))
					S_StartSoundAtVolume(s, (leveltime%2) and sfx_s1b6 or sfx_s1b8, 166)
				end			
			end	
		end
		local LOOP = RAIL.LearnLoopType(GS.myrail)
		if LOOP and LOOP.anim=="context" and (LOOP.walljump)
			if STATS and STATS.WALLCLINGSPRITE and P_IsValidSprite2(s, STATS.WALLCLINGSPRITE)
				STANCE = STATS.WALLCLINGSPRITE
			else
				for i = 0,5
					if (i < 3)
						if (p.charability==CA_GLIDEANDCLIMB) or (string.gmatch(s.skin or "", "knu"))
							STANCE = i==0 and SPR2_CLNG or i==1 and SPR2_CLMB or SPR2_SKID
						else
							STANCE = i==0 and SPR2_SKID or i==1 and SPR2_CLNG or i==2 and SPR2_CLMB
						end
					else
						STANCE = i==3 and SPR2_PAIN or i==4 and SPR2_ROLL or SPR2_STND
					end
					if P_IsValidSprite2(s, STANCE) --or SUPER and P_IsValidSprite2(s, (STANCE|SUPER))
						break --
					end 
				end
			end
			if GS.myrail.GSVertRailDOWNANG!=nil
				p.drawangle = GS.myrail.GSVertRailDOWNANG+ANGLE_180
			end	
		elseif not (P_IsValidSprite2(s, STANCE)) -- and not ( (SUPER) and (P_IsValidSprite2(s, (STANCE|SUPER))) )
			STANCE = SPR2_STND --Backup!
		end
		s.sprite2 = STANCE|SUPER
		s.frame = $ & ~(FF_SPR2MIDSTART|FF_ANIMATE|FF_RANDOMANIM|FF_GLOBALANIM|FF_SPR2ENDSTATE)
		s.tics,s.anim_duration = -1,-1
		if STANCE == SPR2_STND
			s.frame = $ & ~FF_FRAMEMASK
			GS.curanim = nil
		else
			SPRITE2 = skins[s.skin].sprites
			local NAME,MAXFRAME = nil
			if SPRITE2[STANCE] 
				NAME = SPRITE2[STANCE] --STANCE
				MAXFRAME = tonumber(NAME.numframes)
			end
			if STANCE==SPR2_EDGE and ((MAXFRAME or 0) < 5)
				s.frame = $ & ~FF_FRAMEMASK
				GS.curanim = nil
			else
				if NAME==nil
				or not (MAXFRAME) 
				or (type(MAXFRAME)!="number")
				or ((s.frame & FF_FRAMEMASK)+1 >= (MAXFRAME or 0))
					s.frame = ($ & ~FF_FRAMEMASK)
					s.anim_duration = 1 --print("die1")
				else
					s.frame = $+1
					SPRITE2 = skins[s.skin].sprites --This can happen, annoyingly enough.
					if ((s.frame & FF_FRAMEMASK)+1 >= (skins[s.skin].sprites[s.sprite2].numframes or 0))
						s.frame = ($ & ~FF_FRAMEMASK) --THIS however, is pure paranoia.
						--print("die2")						
					else
					/*	print("[1]SPRITE2:\x83"+SPRITE2+"\x80 SPRITE2NAME:\x83"+spr2names[SPRITE2])
						SPRITE2 = SPRITE2[(s.sprite2&SPR2F_SUPER)]
						print("[2]SPRITE2:\x83"+SPRITE2)
						print("[3]SPRITE2NAME:\x83"+spr2names[SPRITE2])
						
						local MAXNEW = skins[s.skin].sprites[SPRITE2].numframes //tonumber(SPRITE2.numframes)
						
						print("[4]SPRITE2:\x83"+SPRITE2+"\x80 SPRITE2NAME:\x83"+spr2names[SPRITE2])
						SPRITE2 = (tonumber(SPRITE2) or 0)-1
						print("[5]SPRITE2:\x83"+SPRITE2+"\x80 SPRITE2NAME:\x83"+spr2names[SPRITE2])
						print("\x84"+"OLDMAXFRAME:\x83"+MAXFRAME+"\x85 New numframo:\x83"+MAXNEW)
						if (MAXNEW >= 0) and ((s.frame & FF_FRAMEMASK) > MAXNEW)
							s.frame = ($ & ~FF_FRAMEMASK)//|max(A, SPRITE2)
						end */
					end
				end
				GS.curanim = {SKIN = s.skin, SPRITE = s.sprite, SPRITE2 = s.sprite2, FRAME = s.frame,
				TIMER = 0, MAX = (ANS[STANCE]) and ANS[STANCE] or 1}
			end
		end
	/*	print("-STANCE:\x84"+spr2names[STANCE]+"\x80 SUPER:\x84"+SUPER+"\x80 s.sprite2:\x83"+s.sprite2+
		"\x80 s.frame:\x85"+(s.frame&~(FF_FULLBRIGHT|FF_TRANSMASK|FF_ANIMATE|FF_HORIZONTALFLIP|FF_VERTICALFLIP))+
		"\x80 spr2names[s.sprite2]:\x84"+spr2names[s.sprite2&~SUPER]+" STANCEno:"+STANCE)
		if type(SPRITE2)=="number"
			print("\x80 F-NAME:\x82 "+SPRITE2+"(number)")
		else
			print("\x80 F-NAME:\x86 "+(SPRITE2[s.sprite2]))
		end */
	end return RAIL.DOSKIN(SKIN, p, s,GS, OGCALL)
end